| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
#line 1 |
|
2
|
|
|
|
|
|
|
# Copyright (c) 2009 by David Golden. All rights reserved. |
|
3
|
|
|
|
|
|
|
# Licensed under Apache License, Version 2.0 (the "License"). |
|
4
|
|
|
|
|
|
|
# You may not use this file except in compliance with the License. |
|
5
|
|
|
|
|
|
|
# A copy of the License was distributed with this file or you may obtain a |
|
6
|
|
|
|
|
|
|
# copy of the License from http://www.apache.org/licenses/LICENSE-2.0 |
|
7
|
|
|
|
|
|
|
|
|
8
|
1
|
|
|
1
|
|
1401
|
package Capture::Tiny; |
|
|
1
|
|
|
|
|
4
|
|
|
|
1
|
|
|
|
|
43
|
|
|
9
|
1
|
|
|
1
|
|
6
|
use 5.006; |
|
|
1
|
|
|
|
|
1
|
|
|
|
1
|
|
|
|
|
38
|
|
|
10
|
1
|
|
|
1
|
|
5
|
use strict; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
34
|
|
|
11
|
1
|
|
|
1
|
|
5
|
use warnings; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
25
|
|
|
12
|
1
|
|
|
1
|
|
5
|
use Carp (); |
|
|
1
|
|
|
|
|
1
|
|
|
|
1
|
|
|
|
|
20
|
|
|
13
|
1
|
|
|
1
|
|
1278
|
use Exporter (); |
|
|
1
|
|
|
|
|
12010
|
|
|
|
1
|
|
|
|
|
34
|
|
|
14
|
1
|
|
|
1
|
|
12
|
use IO::Handle (); |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
26
|
|
|
15
|
1
|
|
|
1
|
|
2203
|
use File::Spec (); |
|
|
1
|
|
|
|
|
18925
|
|
|
|
1
|
|
|
|
|
119
|
|
|
16
|
|
|
|
|
|
|
use File::Temp qw/tempfile tmpnam/; |
|
17
|
1
|
50
|
|
1
|
|
2
|
# Get PerlIO or fake it |
|
|
0
|
|
|
|
|
0
|
|
|
|
1
|
|
|
|
|
12
|
|
|
|
1
|
|
|
|
|
361
|
|
|
18
|
|
|
|
|
|
|
BEGIN { eval { require PerlIO; 1 } or *PerlIO::get_layers = sub { return () }; } |
|
19
|
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
our $VERSION = '0.06'; |
|
21
|
|
|
|
|
|
|
$VERSION = eval $VERSION; ## no critic |
|
22
|
|
|
|
|
|
|
our @ISA = qw/Exporter/; |
|
23
|
|
|
|
|
|
|
our @EXPORT_OK = qw/capture capture_merged tee tee_merged/; |
|
24
|
|
|
|
|
|
|
our %EXPORT_TAGS = ( 'all' => \@EXPORT_OK ); |
|
25
|
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
my $IS_WIN32 = $^O eq 'MSWin32'; |
|
27
|
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
our $DEBUG = $ENV{PERL_CAPTURE_TINY_DEBUG}; |
|
29
|
|
|
|
|
|
|
my $DEBUGFH; |
|
30
|
|
|
|
|
|
|
open $DEBUGFH, ">&STDERR" if $DEBUG; |
|
31
|
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
*_debug = $DEBUG ? sub(@) { print {$DEBUGFH} @_ } : sub(){0}; |
|
33
|
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
#--------------------------------------------------------------------------# |
|
35
|
|
|
|
|
|
|
# command to tee output -- the argument is a filename that must |
|
36
|
|
|
|
|
|
|
# be opened to signal that the process is ready to receive input. |
|
37
|
|
|
|
|
|
|
# This is annoying, but seems to be the best that can be done |
|
38
|
|
|
|
|
|
|
# as a simple, portable IPC technique |
|
39
|
|
|
|
|
|
|
#--------------------------------------------------------------------------# |
|
40
|
|
|
|
|
|
|
my @cmd = ($^X, '-e', '$SIG{HUP}=sub{exit}; ' |
|
41
|
|
|
|
|
|
|
. 'if( my $fn=shift ){ open my $fh, qq{>$fn}; print {$fh} $$; close $fh;} ' |
|
42
|
|
|
|
|
|
|
. 'my $buf; while (sysread(STDIN, $buf, 2048)) { ' |
|
43
|
|
|
|
|
|
|
. 'syswrite(STDOUT, $buf); syswrite(STDERR, $buf)}' |
|
44
|
|
|
|
|
|
|
); |
|
45
|
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
#--------------------------------------------------------------------------# |
|
47
|
|
|
|
|
|
|
# filehandle manipulation |
|
48
|
|
|
|
|
|
|
#--------------------------------------------------------------------------# |
|
49
|
|
|
|
|
|
|
|
|
50
|
4
|
|
|
4
|
|
15
|
sub _relayer { |
|
51
|
4
|
|
|
|
|
11
|
my ($fh, $layers) = @_; |
|
|
4
|
|
|
|
|
68
|
|
|
52
|
4
|
|
|
|
|
47
|
_debug("# requested layers (@{$layers}) to $fh\n"); |
|
53
|
4
|
50
|
66
|
|
|
9
|
my %seen; |
|
|
8
|
|
|
|
|
55
|
|
|
54
|
4
|
|
|
|
|
24
|
my @unique = grep { $_ ne 'unix' and $_ ne 'perlio' and !$seen{$_}++ } @$layers; |
|
55
|
4
|
|
|
|
|
55
|
_debug("# applying unique layers (@unique) to $fh\n"); |
|
56
|
|
|
|
|
|
|
binmode($fh, join(":", "", "raw", @unique)); |
|
57
|
|
|
|
|
|
|
} |
|
58
|
|
|
|
|
|
|
|
|
59
|
42
|
|
|
42
|
|
72
|
sub _name { |
|
60
|
1
|
|
|
1
|
|
5
|
my $glob = shift; |
|
|
1
|
|
|
|
|
3
|
|
|
|
1
|
|
|
|
|
3165
|
|
|
61
|
42
|
|
|
|
|
44
|
no strict 'refs'; ## no critic |
|
|
42
|
|
|
|
|
271
|
|
|
62
|
|
|
|
|
|
|
return *{$glob}{NAME}; |
|
63
|
|
|
|
|
|
|
} |
|
64
|
|
|
|
|
|
|
|
|
65
|
18
|
50
|
|
18
|
|
499
|
sub _open { |
|
66
|
18
|
50
|
|
|
|
53
|
open $_[0], $_[1] or Carp::confess "Error from open(" . join(q{, }, @_) . "): $!"; |
|
|
36
|
|
|
|
|
94
|
|
|
67
|
|
|
|
|
|
|
_debug( "# open " . join( ", " , map { defined $_ ? _name($_) : 'undef' } @_ ) . " as " . fileno( $_[0] ) . "\n" ); |
|
68
|
|
|
|
|
|
|
} |
|
69
|
|
|
|
|
|
|
|
|
70
|
6
|
50
|
|
6
|
|
57
|
sub _close { |
|
71
|
6
|
50
|
|
|
|
30
|
close $_[0] or Carp::confess "Error from close(" . join(q{, }, @_) . "): $!"; |
|
72
|
|
|
|
|
|
|
_debug( "# closed " . ( defined $_[0] ? _name($_[0]) : 'undef' ) . "\n" ); |
|
73
|
|
|
|
|
|
|
} |
|
74
|
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
my %dup; # cache this so STDIN stays fd0 |
|
76
|
|
|
|
|
|
|
my %proxy_count; |
|
77
|
2
|
|
|
2
|
|
3
|
sub _proxy_std { |
|
78
|
2
|
50
|
|
|
|
7
|
my %proxies; |
|
79
|
0
|
|
|
|
|
0
|
if ( ! defined fileno STDIN ) { |
|
80
|
0
|
0
|
|
|
|
0
|
$proxy_count{stdin}++; |
|
81
|
0
|
|
|
|
|
0
|
if (defined $dup{stdin}) { |
|
82
|
0
|
0
|
|
|
|
0
|
_open \*STDIN, "<&=" . fileno($dup{stdin}); |
|
83
|
|
|
|
|
|
|
_debug( "# restored proxy STDIN as " . (defined fileno STDIN ? fileno STDIN : 'undef' ) . "\n" ); |
|
84
|
|
|
|
|
|
|
} |
|
85
|
0
|
|
|
|
|
0
|
else { |
|
86
|
0
|
0
|
|
|
|
0
|
_open \*STDIN, "<" . File::Spec->devnull; |
|
87
|
0
|
|
|
|
|
0
|
_debug( "# proxied STDIN as " . (defined fileno STDIN ? fileno STDIN : 'undef' ) . "\n" ); |
|
88
|
|
|
|
|
|
|
_open $dup{stdin} = IO::Handle->new, "<&=STDIN"; |
|
89
|
0
|
|
|
|
|
0
|
} |
|
90
|
0
|
0
|
|
|
|
0
|
$proxies{stdin} = \*STDIN; |
|
91
|
|
|
|
|
|
|
binmode(STDIN, ':utf8') if $] >= 5.008; |
|
92
|
2
|
50
|
|
|
|
7
|
} |
|
93
|
0
|
|
|
|
|
0
|
if ( ! defined fileno STDOUT ) { |
|
94
|
0
|
0
|
|
|
|
0
|
$proxy_count{stdout}++; |
|
95
|
0
|
|
|
|
|
0
|
if (defined $dup{stdout}) { |
|
96
|
0
|
0
|
|
|
|
0
|
_open \*STDOUT, ">&=" . fileno($dup{stdout}); |
|
97
|
|
|
|
|
|
|
_debug( "# restored proxy STDOUT as " . (defined fileno STDOUT ? fileno STDOUT : 'undef' ) . "\n" ); |
|
98
|
|
|
|
|
|
|
} |
|
99
|
0
|
|
|
|
|
0
|
else { |
|
100
|
0
|
0
|
|
|
|
0
|
_open \*STDOUT, ">" . File::Spec->devnull; |
|
101
|
0
|
|
|
|
|
0
|
_debug( "# proxied STDOUT as " . (defined fileno STDOUT ? fileno STDOUT : 'undef' ) . "\n" ); |
|
102
|
|
|
|
|
|
|
_open $dup{stdout} = IO::Handle->new, ">&=STDOUT"; |
|
103
|
0
|
|
|
|
|
0
|
} |
|
104
|
0
|
0
|
|
|
|
0
|
$proxies{stdout} = \*STDOUT; |
|
105
|
|
|
|
|
|
|
binmode(STDOUT, ':utf8') if $] >= 5.008; |
|
106
|
2
|
50
|
|
|
|
7
|
} |
|
107
|
0
|
|
|
|
|
0
|
if ( ! defined fileno STDERR ) { |
|
108
|
0
|
0
|
|
|
|
0
|
$proxy_count{stderr}++; |
|
109
|
0
|
|
|
|
|
0
|
if (defined $dup{stderr}) { |
|
110
|
0
|
0
|
|
|
|
0
|
_open \*STDERR, ">&=" . fileno($dup{stderr}); |
|
111
|
|
|
|
|
|
|
_debug( "# restored proxy STDERR as " . (defined fileno STDERR ? fileno STDERR : 'undef' ) . "\n" ); |
|
112
|
|
|
|
|
|
|
} |
|
113
|
0
|
|
|
|
|
0
|
else { |
|
114
|
0
|
0
|
|
|
|
0
|
_open \*STDERR, ">" . File::Spec->devnull; |
|
115
|
0
|
|
|
|
|
0
|
_debug( "# proxied STDERR as " . (defined fileno STDERR ? fileno STDERR : 'undef' ) . "\n" ); |
|
116
|
|
|
|
|
|
|
_open $dup{stderr} = IO::Handle->new, ">&=STDERR"; |
|
117
|
0
|
|
|
|
|
0
|
} |
|
118
|
0
|
0
|
|
|
|
0
|
$proxies{stderr} = \*STDERR; |
|
119
|
|
|
|
|
|
|
binmode(STDERR, ':utf8') if $] >= 5.008; |
|
120
|
2
|
|
|
|
|
6
|
} |
|
121
|
|
|
|
|
|
|
return %proxies; |
|
122
|
|
|
|
|
|
|
} |
|
123
|
|
|
|
|
|
|
|
|
124
|
2
|
|
|
2
|
|
8
|
sub _unproxy { |
|
125
|
2
|
|
|
|
|
15
|
my (%proxies) = @_; |
|
126
|
2
|
|
|
|
|
15
|
_debug( "# unproxing " . join(" ", keys %proxies) . "\n" ); |
|
127
|
0
|
|
|
|
|
0
|
for my $p ( keys %proxies ) { |
|
128
|
0
|
|
|
|
|
0
|
$proxy_count{$p}--; |
|
129
|
0
|
0
|
|
|
|
0
|
_debug( "# unproxied " . uc($p) . " ($proxy_count{$p} left)\n" ); |
|
130
|
0
|
|
|
|
|
0
|
if ( ! $proxy_count{$p} ) { |
|
131
|
0
|
0
|
|
|
|
0
|
_close $proxies{$p}; |
|
132
|
0
|
|
|
|
|
0
|
_close $dup{$p} unless $] < 5.008; # 5.6 will have already closed this as dup |
|
133
|
|
|
|
|
|
|
delete $dup{$p}; |
|
134
|
|
|
|
|
|
|
} |
|
135
|
|
|
|
|
|
|
} |
|
136
|
|
|
|
|
|
|
} |
|
137
|
|
|
|
|
|
|
|
|
138
|
2
|
|
|
2
|
|
5
|
sub _copy_std { |
|
|
6
|
|
|
|
|
216
|
|
|
139
|
2
|
|
|
|
|
37
|
my %handles = map { $_, IO::Handle->new } qw/stdin stdout stderr/; |
|
140
|
2
|
|
|
|
|
8
|
_debug( "# copying std handles ...\n" ); |
|
141
|
2
|
|
|
|
|
7
|
_open $handles{stdin}, "<&STDIN"; |
|
142
|
2
|
|
|
|
|
7
|
_open $handles{stdout}, ">&STDOUT"; |
|
143
|
2
|
|
|
|
|
8
|
_open $handles{stderr}, ">&STDERR"; |
|
144
|
|
|
|
|
|
|
return \%handles; |
|
145
|
|
|
|
|
|
|
} |
|
146
|
|
|
|
|
|
|
|
|
147
|
4
|
|
|
4
|
|
16
|
sub _open_std { |
|
148
|
4
|
|
|
|
|
71
|
my ($handles) = @_; |
|
149
|
4
|
|
|
|
|
23
|
_open \*STDIN, "<&" . fileno $handles->{stdin}; |
|
150
|
4
|
|
|
|
|
27
|
_open \*STDOUT, ">&" . fileno $handles->{stdout}; |
|
151
|
|
|
|
|
|
|
_open \*STDERR, ">&" . fileno $handles->{stderr}; |
|
152
|
|
|
|
|
|
|
} |
|
153
|
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
#--------------------------------------------------------------------------# |
|
155
|
|
|
|
|
|
|
# private subs |
|
156
|
|
|
|
|
|
|
#--------------------------------------------------------------------------# |
|
157
|
|
|
|
|
|
|
|
|
158
|
0
|
|
|
0
|
|
0
|
sub _start_tee { |
|
159
|
|
|
|
|
|
|
my ($which, $stash) = @_; |
|
160
|
0
|
|
|
|
|
0
|
# setup pipes |
|
161
|
0
|
|
|
|
|
0
|
$stash->{$_}{$which} = IO::Handle->new for qw/tee reader/; |
|
162
|
0
|
|
|
|
|
0
|
pipe $stash->{reader}{$which}, $stash->{tee}{$which}; |
|
163
|
|
|
|
|
|
|
_debug( "# pipe for $which\: " . _name($stash->{tee}{$which}) . " " |
|
164
|
|
|
|
|
|
|
. fileno( $stash->{tee}{$which} ) . " => " . _name($stash->{reader}{$which}) |
|
165
|
0
|
|
|
|
|
0
|
. " " . fileno( $stash->{reader}{$which}) . "\n" ); |
|
166
|
|
|
|
|
|
|
select((select($stash->{tee}{$which}), $|=1)[0]); # autoflush |
|
167
|
0
|
|
|
|
|
0
|
# setup desired redirection for parent and child |
|
168
|
0
|
|
|
|
|
0
|
$stash->{new}{$which} = $stash->{tee}{$which}; |
|
169
|
|
|
|
|
|
|
$stash->{child}{$which} = { |
|
170
|
|
|
|
|
|
|
stdin => $stash->{reader}{$which}, |
|
171
|
|
|
|
|
|
|
stdout => $stash->{old}{$which}, |
|
172
|
|
|
|
|
|
|
stderr => $stash->{capture}{$which}, |
|
173
|
|
|
|
|
|
|
}; |
|
174
|
0
|
|
|
|
|
0
|
# flag file is used to signal the child is ready |
|
175
|
|
|
|
|
|
|
$stash->{flag_files}{$which} = scalar tmpnam(); |
|
176
|
0
|
0
|
|
|
|
0
|
# execute @cmd as a separate process |
|
177
|
0
|
|
|
|
|
0
|
if ( $IS_WIN32 ) { |
|
178
|
0
|
0
|
|
|
|
0
|
eval "use Win32API::File qw/CloseHandle GetOsFHandle SetHandleInformation fileLastError HANDLE_FLAG_INHERIT INVALID_HANDLE_VALUE/ "; |
|
179
|
0
|
|
|
|
|
0
|
_debug( "# Win32API::File loaded\n") unless $@; |
|
180
|
0
|
0
|
0
|
|
|
0
|
my $os_fhandle = GetOsFHandle( $stash->{tee}{$which} ); |
|
181
|
0
|
0
|
|
|
|
0
|
_debug( "# Couldn't get OS handle: " . fileLastError() . "\n") if ! defined $os_fhandle || $os_fhandle == INVALID_HANDLE_VALUE(); |
|
182
|
0
|
|
|
|
|
0
|
if ( SetHandleInformation( $os_fhandle, HANDLE_FLAG_INHERIT(), 0) ) { |
|
183
|
|
|
|
|
|
|
_debug( "# set no-inherit flag on $which tee\n" ); |
|
184
|
|
|
|
|
|
|
} |
|
185
|
0
|
|
|
|
|
0
|
else { |
|
186
|
|
|
|
|
|
|
_debug( "# can't disable tee handle flag inherit: " . fileLastError() . "\n"); |
|
187
|
0
|
|
|
|
|
0
|
} |
|
188
|
0
|
|
|
|
|
0
|
_open_std( $stash->{child}{$which} ); |
|
189
|
|
|
|
|
|
|
$stash->{pid}{$which} = system(1, @cmd, $stash->{flag_files}{$which}); |
|
190
|
|
|
|
|
|
|
# not restoring std here as it all gets redirected again shortly anyway |
|
191
|
|
|
|
|
|
|
} |
|
192
|
0
|
|
|
|
|
0
|
else { # use fork |
|
193
|
|
|
|
|
|
|
_fork_exec( $which, $stash ); |
|
194
|
|
|
|
|
|
|
} |
|
195
|
|
|
|
|
|
|
} |
|
196
|
|
|
|
|
|
|
|
|
197
|
0
|
|
|
0
|
|
0
|
sub _fork_exec { |
|
198
|
0
|
|
|
|
|
0
|
my ($which, $stash) = @_; |
|
199
|
0
|
0
|
|
|
|
0
|
my $pid = fork; |
|
|
|
0
|
|
|
|
|
|
|
200
|
0
|
|
|
|
|
0
|
if ( not defined $pid ) { |
|
201
|
|
|
|
|
|
|
Carp::confess "Couldn't fork(): $!"; |
|
202
|
|
|
|
|
|
|
} |
|
203
|
0
|
|
|
|
|
0
|
elsif ($pid == 0) { # child |
|
204
|
0
|
|
|
|
|
0
|
_debug( "# in child process ...\n" ); |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
205
|
0
|
|
|
|
|
0
|
untie *STDIN; untie *STDOUT; untie *STDERR; |
|
206
|
0
|
|
|
|
|
0
|
_close $stash->{tee}{$which}; |
|
207
|
0
|
|
|
|
|
0
|
_debug( "# redirecting handles in child ...\n" ); |
|
208
|
0
|
|
|
|
|
0
|
_open_std( $stash->{child}{$which} ); |
|
209
|
0
|
|
|
|
|
0
|
_debug( "# calling exec on command ...\n" ); |
|
210
|
|
|
|
|
|
|
exec @cmd, $stash->{flag_files}{$which}; |
|
211
|
0
|
|
|
|
|
0
|
} |
|
212
|
|
|
|
|
|
|
$stash->{pid}{$which} = $pid |
|
213
|
|
|
|
|
|
|
} |
|
214
|
0
|
|
0
|
0
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
215
|
|
|
|
|
|
|
sub _files_exist { -f $_ || return 0 for @_; return 1 } |
|
216
|
|
|
|
|
|
|
|
|
217
|
0
|
|
|
0
|
|
0
|
sub _wait_for_tees { |
|
218
|
0
|
|
|
|
|
0
|
my ($stash) = @_; |
|
219
|
0
|
|
|
|
|
0
|
my $start = time; |
|
|
0
|
|
|
|
|
0
|
|
|
220
|
0
|
|
0
|
|
|
0
|
my @files = values %{$stash->{flag_files}}; |
|
221
|
0
|
0
|
|
|
|
0
|
1 until _files_exist(@files) || (time - $start > 30); |
|
222
|
0
|
|
|
|
|
0
|
Carp::confess "Timed out waiting for subprocesses to start" if ! _files_exist(@files); |
|
223
|
|
|
|
|
|
|
unlink $_ for @files; |
|
224
|
|
|
|
|
|
|
} |
|
225
|
|
|
|
|
|
|
|
|
226
|
0
|
|
|
0
|
|
0
|
sub _kill_tees { |
|
227
|
0
|
0
|
|
|
|
0
|
my ($stash) = @_; |
|
228
|
0
|
|
|
|
|
0
|
if ( $IS_WIN32 ) { |
|
229
|
0
|
|
|
|
|
0
|
_debug( "# closing handles with CloseHandle\n"); |
|
|
0
|
|
|
|
|
0
|
|
|
230
|
0
|
|
|
|
|
0
|
CloseHandle( GetOsFHandle($_) ) for values %{ $stash->{tee} }; |
|
231
|
0
|
|
|
|
|
0
|
_debug( "# waiting for subprocesses to finish\n"); |
|
232
|
0
|
|
0
|
|
|
0
|
my $start = time; |
|
233
|
|
|
|
|
|
|
1 until wait == -1 || (time - $start > 30); |
|
234
|
|
|
|
|
|
|
} |
|
235
|
0
|
|
|
|
|
0
|
else { |
|
|
0
|
|
|
|
|
0
|
|
|
236
|
0
|
|
|
|
|
0
|
_close $_ for values %{ $stash->{tee} }; |
|
|
0
|
|
|
|
|
0
|
|
|
237
|
|
|
|
|
|
|
waitpid $_, 0 for values %{ $stash->{pid} }; |
|
238
|
|
|
|
|
|
|
} |
|
239
|
|
|
|
|
|
|
} |
|
240
|
|
|
|
|
|
|
|
|
241
|
2
|
|
|
2
|
|
82
|
sub _slurp { |
|
|
2
|
|
|
|
|
23
|
|
|
|
2
|
|
|
|
|
109
|
|
|
242
|
|
|
|
|
|
|
seek $_[0],0,0; local $/; return scalar readline $_[0]; |
|
243
|
|
|
|
|
|
|
} |
|
244
|
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
#--------------------------------------------------------------------------# |
|
246
|
|
|
|
|
|
|
# _capture_tee() -- generic main sub for capturing or teeing |
|
247
|
|
|
|
|
|
|
#--------------------------------------------------------------------------# |
|
248
|
|
|
|
|
|
|
|
|
249
|
2
|
|
|
2
|
|
19
|
sub _capture_tee { |
|
250
|
2
|
|
|
|
|
9
|
_debug( "# starting _capture_tee with (@_)...\n" ); |
|
251
|
|
|
|
|
|
|
my ($tee_stdout, $tee_stderr, $merge, $code) = @_; |
|
252
|
2
|
|
|
|
|
15
|
# save existing filehandles and setup captures |
|
253
|
2
|
|
|
|
|
7
|
local *CT_ORIG_STDIN = *STDIN ; |
|
254
|
2
|
|
|
|
|
7
|
local *CT_ORIG_STDOUT = *STDOUT; |
|
255
|
|
|
|
|
|
|
local *CT_ORIG_STDERR = *STDERR; |
|
256
|
2
|
|
|
|
|
34
|
# find initial layers |
|
257
|
|
|
|
|
|
|
my %layers = ( |
|
258
|
|
|
|
|
|
|
stdin => [PerlIO::get_layers(\*STDIN) ], |
|
259
|
|
|
|
|
|
|
stdout => [PerlIO::get_layers(\*STDOUT)], |
|
260
|
|
|
|
|
|
|
stderr => [PerlIO::get_layers(\*STDERR)], |
|
261
|
2
|
|
|
|
|
11
|
); |
|
|
6
|
|
|
|
|
26
|
|
|
262
|
|
|
|
|
|
|
_debug( "# existing layers for $_\: @{$layers{$_}}\n" ) for qw/stdin stdout stderr/; |
|
263
|
2
|
|
|
|
|
4
|
# bypass scalar filehandles and tied handles |
|
264
|
2
|
50
|
|
|
|
2
|
my %localize; |
|
|
4
|
|
|
|
|
13
|
|
|
|
2
|
|
|
|
|
5
|
|
|
265
|
2
|
50
|
|
|
|
3
|
$localize{stdin}++, local(*STDIN) if grep { $_ eq 'scalar' } @{$layers{stdin}}; |
|
|
4
|
|
|
|
|
10
|
|
|
|
2
|
|
|
|
|
7
|
|
|
266
|
2
|
50
|
|
|
|
4
|
$localize{stdout}++, local(*STDOUT) if grep { $_ eq 'scalar' } @{$layers{stdout}}; |
|
|
4
|
|
|
|
|
8
|
|
|
|
2
|
|
|
|
|
4
|
|
|
267
|
2
|
50
|
33
|
|
|
9
|
$localize{stderr}++, local(*STDERR) if grep { $_ eq 'scalar' } @{$layers{stderr}}; |
|
268
|
2
|
50
|
33
|
|
|
8
|
$localize{stdout}++, local(*STDOUT), _open( \*STDOUT, ">&=1") if tied *STDOUT && $] >= 5.008; |
|
269
|
2
|
|
|
|
|
5
|
$localize{stderr}++, local(*STDERR), _open( \*STDERR, ">&=2") if tied *STDERR && $] >= 5.008; |
|
270
|
2
|
|
|
|
|
11
|
_debug( "# localized $_\n" ) for keys %localize; |
|
271
|
2
|
|
|
|
|
3
|
my %proxy_std = _proxy_std(); |
|
|
2
|
|
|
|
|
8
|
|
|
272
|
2
|
|
|
|
|
7
|
_debug( "# proxy std is @{ [%proxy_std] }\n" ); |
|
273
|
|
|
|
|
|
|
my $stash = { old => _copy_std() }; |
|
274
|
2
|
|
|
|
|
42
|
# update layers after any proxying |
|
275
|
|
|
|
|
|
|
%layers = ( |
|
276
|
|
|
|
|
|
|
stdin => [PerlIO::get_layers(\*STDIN) ], |
|
277
|
|
|
|
|
|
|
stdout => [PerlIO::get_layers(\*STDOUT)], |
|
278
|
|
|
|
|
|
|
stderr => [PerlIO::get_layers(\*STDERR)], |
|
279
|
2
|
|
|
|
|
12
|
); |
|
|
6
|
|
|
|
|
25
|
|
|
280
|
|
|
|
|
|
|
_debug( "# post-proxy layers for $_\: @{$layers{$_}}\n" ) for qw/stdin stdout stderr/; |
|
281
|
2
|
|
|
|
|
27
|
# get handles for capture and apply existing IO layers |
|
282
|
2
|
|
|
|
|
2799
|
$stash->{new}{$_} = $stash->{capture}{$_} = File::Temp->new for qw/stdout stderr/; |
|
283
|
|
|
|
|
|
|
_debug("# will capture $_ on " .fileno($stash->{capture}{$_})."\n" ) for qw/stdout stderr/; |
|
284
|
2
|
50
|
|
|
|
10
|
# tees may change $stash->{new} |
|
285
|
2
|
50
|
|
|
|
5
|
_start_tee( stdout => $stash ) if $tee_stdout; |
|
286
|
2
|
50
|
33
|
|
|
19
|
_start_tee( stderr => $stash ) if $tee_stderr; |
|
287
|
|
|
|
|
|
|
_wait_for_tees( $stash ) if $tee_stdout || $tee_stderr; |
|
288
|
2
|
50
|
|
|
|
11
|
# finalize redirection |
|
289
|
2
|
|
|
|
|
8
|
$stash->{new}{stderr} = $stash->{new}{stdout} if $merge; |
|
290
|
2
|
|
|
|
|
6
|
$stash->{new}{stdin} = $stash->{old}{stdin}; |
|
291
|
2
|
|
|
|
|
8
|
_debug( "# redirecting in parent ...\n" ); |
|
292
|
|
|
|
|
|
|
_open_std( $stash->{new} ); |
|
293
|
2
|
|
|
|
|
4
|
# execute user provided code |
|
294
|
|
|
|
|
|
|
my $exit_code; |
|
295
|
2
|
50
|
|
|
|
3
|
{ |
|
|
2
|
|
|
|
|
8
|
|
|
296
|
2
|
50
|
|
|
|
6
|
local *STDIN = *CT_ORIG_STDIN if $localize{stdin}; # get original, not proxy STDIN |
|
297
|
2
|
|
|
|
|
5
|
local *STDERR = *STDOUT if $merge; # minimize buffer mixups during $code |
|
298
|
2
|
|
|
|
|
7
|
_debug( "# finalizing layers ...\n" ); |
|
299
|
2
|
50
|
|
|
|
6
|
_relayer(\*STDOUT, $layers{stdout}); |
|
300
|
2
|
|
|
|
|
10
|
_relayer(\*STDERR, $layers{stderr}) unless $merge; |
|
301
|
2
|
|
|
|
|
7
|
_debug( "# running code $code ...\n" ); |
|
302
|
2
|
|
|
|
|
1158380
|
$code->(); |
|
303
|
|
|
|
|
|
|
$exit_code = $?; # save this for later |
|
304
|
|
|
|
|
|
|
} |
|
305
|
2
|
|
|
|
|
40
|
# restore prior filehandles and shut down tees |
|
306
|
2
|
|
|
|
|
59
|
_debug( "# restoring ...\n" ); |
|
307
|
2
|
|
|
|
|
7
|
_open_std( $stash->{old} ); |
|
|
2
|
|
|
|
|
31
|
|
|
308
|
2
|
|
|
|
|
49
|
_close( $_ ) for values %{$stash->{old}}; # don't leak fds |
|
309
|
2
|
50
|
33
|
|
|
37
|
_unproxy( %proxy_std ); |
|
310
|
|
|
|
|
|
|
_kill_tees( $stash ) if $tee_stdout || $tee_stderr; |
|
311
|
2
|
|
|
|
|
21
|
# return captured output |
|
312
|
2
|
50
|
|
|
|
8
|
_relayer($stash->{capture}{stdout}, $layers{stdout}); |
|
313
|
2
|
|
|
|
|
12
|
_relayer($stash->{capture}{stderr}, $layers{stderr}) unless $merge; |
|
|
4
|
|
|
|
|
48
|
|
|
314
|
2
|
|
|
|
|
23
|
_debug( "# slurping captured $_ with layers: @{[PerlIO::get_layers($stash->{capture}{$_})]}\n") for qw/stdout stderr/; |
|
315
|
2
|
50
|
|
|
|
14
|
my $got_out = _slurp($stash->{capture}{stdout}); |
|
316
|
2
|
50
|
33
|
|
|
15
|
my $got_err = $merge ? q() : _slurp($stash->{capture}{stderr}); |
|
317
|
2
|
0
|
33
|
|
|
10
|
print CT_ORIG_STDOUT $got_out if $localize{stdout} && $tee_stdout; |
|
|
|
|
33
|
|
|
|
|
|
318
|
2
|
|
|
|
|
4
|
print CT_ORIG_STDERR $got_err if !$merge && $localize{stderr} && $tee_stdout; |
|
319
|
2
|
|
|
|
|
16
|
$? = $exit_code; |
|
320
|
2
|
50
|
|
|
|
158
|
_debug( "# ending _capture_tee with (@_)...\n" ); |
|
321
|
0
|
0
|
|
|
|
0
|
return $got_out if $merge; |
|
322
|
|
|
|
|
|
|
return wantarray ? ($got_out, $got_err) : $got_out; |
|
323
|
|
|
|
|
|
|
} |
|
324
|
|
|
|
|
|
|
|
|
325
|
|
|
|
|
|
|
#--------------------------------------------------------------------------# |
|
326
|
|
|
|
|
|
|
# create API subroutines from [tee STDOUT flag, tee STDERR, merge flag] |
|
327
|
|
|
|
|
|
|
#--------------------------------------------------------------------------# |
|
328
|
|
|
|
|
|
|
|
|
329
|
|
|
|
|
|
|
my %api = ( |
|
330
|
|
|
|
|
|
|
capture => [0,0,0], |
|
331
|
|
|
|
|
|
|
capture_merged => [0,0,1], |
|
332
|
|
|
|
|
|
|
tee => [1,1,0], |
|
333
|
|
|
|
|
|
|
tee_merged => [1,0,1], # don't tee STDOUT since merging |
|
334
|
|
|
|
|
|
|
); |
|
335
|
|
|
|
|
|
|
|
|
336
|
|
|
|
|
|
|
for my $sub ( keys %api ) { |
|
337
|
0
|
|
|
0
|
1
|
0
|
my $args = join q{, }, @{$api{$sub}}; |
|
|
0
|
|
|
2
|
1
|
0
|
|
|
|
2
|
|
|
0
|
1
|
5154
|
|
|
|
2
|
|
|
0
|
1
|
12
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
338
|
|
|
|
|
|
|
eval "sub $sub(&) {unshift \@_, $args; goto \\&_capture_tee;}"; ## no critic |
|
339
|
|
|
|
|
|
|
} |
|
340
|
|
|
|
|
|
|
|
|
341
|
|
|
|
|
|
|
1; |
|
342
|
|
|
|
|
|
|
|
|
343
|
|
|
|
|
|
|
__END__ |