2 | 2 |
use warnings;
|
3 | 3 |
package Capture::Tiny;
|
4 | 4 |
# ABSTRACT: Capture STDOUT and STDERR from Perl, XS or external programs
|
5 | |
our $VERSION = '0.14'; # VERSION
|
|
5 |
our $VERSION = '0.15'; # VERSION
|
6 | 6 |
use Carp ();
|
7 | 7 |
use Exporter ();
|
8 | 8 |
use IO::Handle ();
|
|
47 | 47 |
|
48 | 48 |
my $IS_WIN32 = $^O eq 'MSWin32';
|
49 | 49 |
|
50 | |
our $DEBUG = $ENV{PERL_CAPTURE_TINY_DEBUG};
|
51 | |
|
52 | |
my $DEBUGFH;
|
53 | |
open $DEBUGFH, "> DEBUG" if $DEBUG;
|
54 | |
|
55 | |
*_debug = $DEBUG ? sub(@) { print {$DEBUGFH} @_ } : sub(){0};
|
|
50 |
#our $DEBUG = $ENV{PERL_CAPTURE_TINY_DEBUG};
|
|
51 |
#
|
|
52 |
#my $DEBUGFH;
|
|
53 |
#open $DEBUGFH, "> DEBUG" if $DEBUG;
|
|
54 |
#
|
|
55 |
#*_debug = $DEBUG ? sub(@) { print {$DEBUGFH} @_ } : sub(){0};
|
56 | 56 |
|
57 | 57 |
our $TIMEOUT = 30;
|
58 | 58 |
|
|
74 | 74 |
|
75 | 75 |
sub _relayer {
|
76 | 76 |
my ($fh, $layers) = @_;
|
77 | |
_debug("# requested layers (@{$layers}) for @{[fileno $fh]}\n");
|
|
77 |
# _debug("# requested layers (@{$layers}) for @{[fileno $fh]}\n");
|
78 | 78 |
my %seen = ( unix => 1, perlio => 1 ); # filter these out
|
79 | 79 |
my @unique = grep { !$seen{$_}++ } @$layers;
|
80 | |
_debug("# applying unique layers (@unique) to @{[fileno $fh]}\n");
|
|
80 |
# _debug("# applying unique layers (@unique) to @{[fileno $fh]}\n");
|
81 | 81 |
binmode($fh, join(":", ":raw", @unique));
|
82 | 82 |
}
|
83 | 83 |
|
|
89 | 89 |
|
90 | 90 |
sub _open {
|
91 | 91 |
open $_[0], $_[1] or Carp::confess "Error from open(" . join(q{, }, @_) . "): $!";
|
92 | |
_debug( "# open " . join( ", " , map { defined $_ ? _name($_) : 'undef' } @_ ) . " as " . fileno( $_[0] ) . "\n" );
|
|
92 |
# _debug( "# open " . join( ", " , map { defined $_ ? _name($_) : 'undef' } @_ ) . " as " . fileno( $_[0] ) . "\n" );
|
93 | 93 |
}
|
94 | 94 |
|
95 | 95 |
sub _close {
|
96 | 96 |
close $_[0] or Carp::confess "Error from close(" . join(q{, }, @_) . "): $!";
|
97 | |
_debug( "# closed " . ( defined $_[0] ? _name($_[0]) : 'undef' ) . "\n" );
|
|
97 |
# _debug( "# closed " . ( defined $_[0] ? _name($_[0]) : 'undef' ) . "\n" );
|
98 | 98 |
}
|
99 | 99 |
|
100 | 100 |
my %dup; # cache this so STDIN stays fd0
|
|
105 | 105 |
$proxy_count{stdin}++;
|
106 | 106 |
if (defined $dup{stdin}) {
|
107 | 107 |
_open \*STDIN, "<&=" . fileno($dup{stdin});
|
108 | |
_debug( "# restored proxy STDIN as " . (defined fileno STDIN ? fileno STDIN : 'undef' ) . "\n" );
|
|
108 |
# _debug( "# restored proxy STDIN as " . (defined fileno STDIN ? fileno STDIN : 'undef' ) . "\n" );
|
109 | 109 |
}
|
110 | 110 |
else {
|
111 | 111 |
_open \*STDIN, "<" . File::Spec->devnull;
|
112 | |
_debug( "# proxied STDIN as " . (defined fileno STDIN ? fileno STDIN : 'undef' ) . "\n" );
|
|
112 |
# _debug( "# proxied STDIN as " . (defined fileno STDIN ? fileno STDIN : 'undef' ) . "\n" );
|
113 | 113 |
_open $dup{stdin} = IO::Handle->new, "<&=STDIN";
|
114 | 114 |
}
|
115 | 115 |
$proxies{stdin} = \*STDIN;
|
|
119 | 119 |
$proxy_count{stdout}++;
|
120 | 120 |
if (defined $dup{stdout}) {
|
121 | 121 |
_open \*STDOUT, ">&=" . fileno($dup{stdout});
|
122 | |
_debug( "# restored proxy STDOUT as " . (defined fileno STDOUT ? fileno STDOUT : 'undef' ) . "\n" );
|
|
122 |
# _debug( "# restored proxy STDOUT as " . (defined fileno STDOUT ? fileno STDOUT : 'undef' ) . "\n" );
|
123 | 123 |
}
|
124 | 124 |
else {
|
125 | 125 |
_open \*STDOUT, ">" . File::Spec->devnull;
|
126 | |
_debug( "# proxied STDOUT as " . (defined fileno STDOUT ? fileno STDOUT : 'undef' ) . "\n" );
|
|
126 |
# _debug( "# proxied STDOUT as " . (defined fileno STDOUT ? fileno STDOUT : 'undef' ) . "\n" );
|
127 | 127 |
_open $dup{stdout} = IO::Handle->new, ">&=STDOUT";
|
128 | 128 |
}
|
129 | 129 |
$proxies{stdout} = \*STDOUT;
|
|
133 | 133 |
$proxy_count{stderr}++;
|
134 | 134 |
if (defined $dup{stderr}) {
|
135 | 135 |
_open \*STDERR, ">&=" . fileno($dup{stderr});
|
136 | |
_debug( "# restored proxy STDERR as " . (defined fileno STDERR ? fileno STDERR : 'undef' ) . "\n" );
|
|
136 |
# _debug( "# restored proxy STDERR as " . (defined fileno STDERR ? fileno STDERR : 'undef' ) . "\n" );
|
137 | 137 |
}
|
138 | 138 |
else {
|
139 | 139 |
_open \*STDERR, ">" . File::Spec->devnull;
|
140 | |
_debug( "# proxied STDERR as " . (defined fileno STDERR ? fileno STDERR : 'undef' ) . "\n" );
|
|
140 |
# _debug( "# proxied STDERR as " . (defined fileno STDERR ? fileno STDERR : 'undef' ) . "\n" );
|
141 | 141 |
_open $dup{stderr} = IO::Handle->new, ">&=STDERR";
|
142 | 142 |
}
|
143 | 143 |
$proxies{stderr} = \*STDERR;
|
|
148 | 148 |
|
149 | 149 |
sub _unproxy {
|
150 | 150 |
my (%proxies) = @_;
|
151 | |
_debug( "# unproxing " . join(" ", keys %proxies) . "\n" );
|
|
151 |
# _debug( "# unproxing " . join(" ", keys %proxies) . "\n" );
|
152 | 152 |
for my $p ( keys %proxies ) {
|
153 | 153 |
$proxy_count{$p}--;
|
154 | |
_debug( "# unproxied " . uc($p) . " ($proxy_count{$p} left)\n" );
|
|
154 |
# _debug( "# unproxied " . uc($p) . " ($proxy_count{$p} left)\n" );
|
155 | 155 |
if ( ! $proxy_count{$p} ) {
|
156 | 156 |
_close $proxies{$p};
|
157 | 157 |
_close $dup{$p} unless $] < 5.008; # 5.6 will have already closed this as dup
|
|
162 | 162 |
|
163 | 163 |
sub _copy_std {
|
164 | 164 |
my %handles = map { $_, IO::Handle->new } qw/stdin stdout stderr/;
|
165 | |
_debug( "# copying std handles ...\n" );
|
|
165 |
# _debug( "# copying std handles ...\n" );
|
166 | 166 |
_open $handles{stdin}, "<&STDIN";
|
167 | 167 |
_open $handles{stdout}, ">&STDOUT";
|
168 | 168 |
_open $handles{stderr}, ">&STDERR";
|
|
185 | 185 |
# setup pipes
|
186 | 186 |
$stash->{$_}{$which} = IO::Handle->new for qw/tee reader/;
|
187 | 187 |
pipe $stash->{reader}{$which}, $stash->{tee}{$which};
|
188 | |
_debug( "# pipe for $which\: " . _name($stash->{tee}{$which}) . " "
|
189 | |
. fileno( $stash->{tee}{$which} ) . " => " . _name($stash->{reader}{$which})
|
190 | |
. " " . fileno( $stash->{reader}{$which}) . "\n" );
|
|
188 |
# _debug( "# pipe for $which\: " . _name($stash->{tee}{$which}) . " "
|
|
189 |
# . fileno( $stash->{tee}{$which} ) . " => " . _name($stash->{reader}{$which})
|
|
190 |
# . " " . fileno( $stash->{reader}{$which}) . "\n" );
|
191 | 191 |
select((select($stash->{tee}{$which}), $|=1)[0]); # autoflush
|
192 | 192 |
# setup desired redirection for parent and child
|
193 | 193 |
$stash->{new}{$which} = $stash->{tee}{$which};
|
|
202 | 202 |
if ( $IS_WIN32 ) {
|
203 | 203 |
local $@;
|
204 | 204 |
eval "use Win32API::File qw/CloseHandle GetOsFHandle SetHandleInformation fileLastError HANDLE_FLAG_INHERIT INVALID_HANDLE_VALUE/ ";
|
205 | |
_debug( "# Win32API::File loaded\n") unless $@;
|
|
205 |
# _debug( "# Win32API::File loaded\n") unless $@;
|
206 | 206 |
my $os_fhandle = GetOsFHandle( $stash->{tee}{$which} );
|
207 | |
_debug( "# Couldn't get OS handle: " . fileLastError() . "\n") if ! defined $os_fhandle || $os_fhandle == INVALID_HANDLE_VALUE();
|
|
207 |
# _debug( "# Couldn't get OS handle: " . fileLastError() . "\n") if ! defined $os_fhandle || $os_fhandle == INVALID_HANDLE_VALUE();
|
208 | 208 |
if ( SetHandleInformation( $os_fhandle, HANDLE_FLAG_INHERIT(), 0) ) {
|
209 | |
_debug( "# set no-inherit flag on $which tee\n" );
|
|
209 |
# _debug( "# set no-inherit flag on $which tee\n" );
|
210 | 210 |
}
|
211 | 211 |
else {
|
212 | |
_debug( "# can't disable tee handle flag inherit: " . fileLastError() . "\n");
|
|
212 |
# _debug( "# can't disable tee handle flag inherit: " . fileLastError() . "\n");
|
213 | 213 |
}
|
214 | 214 |
_open_std( $stash->{child}{$which} );
|
215 | 215 |
$stash->{pid}{$which} = system(1, @cmd, $stash->{flag_files}{$which});
|
|
227 | 227 |
Carp::confess "Couldn't fork(): $!";
|
228 | 228 |
}
|
229 | 229 |
elsif ($pid == 0) { # child
|
230 | |
_debug( "# in child process ...\n" );
|
|
230 |
# _debug( "# in child process ...\n" );
|
231 | 231 |
untie *STDIN; untie *STDOUT; untie *STDERR;
|
232 | 232 |
_close $stash->{tee}{$which};
|
233 | |
_debug( "# redirecting handles in child ...\n" );
|
|
233 |
# _debug( "# redirecting handles in child ...\n" );
|
234 | 234 |
_open_std( $stash->{child}{$which} );
|
235 | |
_debug( "# calling exec on command ...\n" );
|
|
235 |
# _debug( "# calling exec on command ...\n" );
|
236 | 236 |
exec @cmd, $stash->{flag_files}{$which};
|
237 | 237 |
}
|
238 | 238 |
$stash->{pid}{$which} = $pid
|
|
259 | 259 |
sub _kill_tees {
|
260 | 260 |
my ($stash) = @_;
|
261 | 261 |
if ( $IS_WIN32 ) {
|
262 | |
_debug( "# closing handles with CloseHandle\n");
|
|
262 |
# _debug( "# closing handles with CloseHandle\n");
|
263 | 263 |
CloseHandle( GetOsFHandle($_) ) for values %{ $stash->{tee} };
|
264 | |
_debug( "# waiting for subprocesses to finish\n");
|
|
264 |
# _debug( "# waiting for subprocesses to finish\n");
|
265 | 265 |
my $start = time;
|
266 | 266 |
1 until wait == -1 || (time - $start > 30);
|
267 | 267 |
}
|
|
274 | 274 |
sub _slurp {
|
275 | 275 |
my ($name, $stash) = @_;
|
276 | 276 |
my ($fh, $pos) = map { $stash->{$_}{$name} } qw/capture pos/;
|
277 | |
_debug( "# slurping captured $name from $pos with layers: @{[PerlIO::get_layers($fh)]}\n");
|
278 | |
return do { local $/; seek $fh,$pos,0; scalar readline $fh };
|
|
277 |
# _debug( "# slurping captured $name from $pos with layers: @{[PerlIO::get_layers($fh)]}\n");
|
|
278 |
seek( $fh, $pos, 0 ) or die "Couldn't seek on capture handle for $name\n";
|
|
279 |
my $text = do { local $/; scalar readline $fh };
|
|
280 |
return defined($text) ? $text : "";
|
279 | 281 |
}
|
280 | 282 |
|
281 | 283 |
#--------------------------------------------------------------------------#
|
|
283 | 285 |
#--------------------------------------------------------------------------#
|
284 | 286 |
|
285 | 287 |
sub _capture_tee {
|
286 | |
_debug( "# starting _capture_tee with (@_)...\n" );
|
|
288 |
# _debug( "# starting _capture_tee with (@_)...\n" );
|
287 | 289 |
my ($do_stdout, $do_stderr, $do_merge, $do_tee, $code, @opts) = @_;
|
288 | 290 |
my %do = ($do_stdout ? (stdout => 1) : (), $do_stderr ? (stderr => 1) : ());
|
289 | 291 |
Carp::confess("Custom capture options must be given as key/value pairs\n")
|
|
304 | 306 |
stdout => [PerlIO::get_layers(\*STDOUT)],
|
305 | 307 |
stderr => [PerlIO::get_layers(\*STDERR)],
|
306 | 308 |
);
|
307 | |
_debug( "# existing layers for $_\: @{$layers{$_}}\n" ) for qw/stdin stdout stderr/;
|
|
309 |
# _debug( "# existing layers for $_\: @{$layers{$_}}\n" ) for qw/stdin stdout stderr/;
|
308 | 310 |
# get layers from underlying glob of tied filehandles if we can
|
309 | 311 |
# (this only works for things that work like Tie::StdHandle)
|
310 | 312 |
$layers{stdout} = [PerlIO::get_layers(tied *STDOUT)]
|
311 | 313 |
if tied(*STDOUT) && (reftype tied *STDOUT eq 'GLOB');
|
312 | 314 |
$layers{stderr} = [PerlIO::get_layers(tied *STDERR)]
|
313 | 315 |
if tied(*STDERR) && (reftype tied *STDERR eq 'GLOB');
|
314 | |
_debug( "# tied object corrected layers for $_\: @{$layers{$_}}\n" ) for qw/stdin stdout stderr/;
|
|
316 |
# _debug( "# tied object corrected layers for $_\: @{$layers{$_}}\n" ) for qw/stdin stdout stderr/;
|
315 | 317 |
# bypass scalar filehandles and tied handles
|
316 | 318 |
my %localize;
|
317 | 319 |
$localize{stdin}++, local(*STDIN)
|
|
324 | 326 |
if $do_stdout && tied *STDOUT && $] >= 5.008;
|
325 | 327 |
$localize{stderr}++, local(*STDERR), _open( \*STDERR, ">&=2")
|
326 | 328 |
if ($do_stderr || $do_merge) && tied *STDERR && $] >= 5.008;
|
327 | |
_debug( "# localized $_\n" ) for keys %localize;
|
|
329 |
# _debug( "# localized $_\n" ) for keys %localize;
|
328 | 330 |
# proxy any closed/localized handles so we don't use fds 0, 1 or 2
|
329 | 331 |
my %proxy_std = _proxy_std();
|
330 | |
_debug( "# proxy std: @{ [%proxy_std] }\n" );
|
|
332 |
# _debug( "# proxy std: @{ [%proxy_std] }\n" );
|
331 | 333 |
# update layers after any proxying
|
332 | 334 |
$layers{stdin} = [PerlIO::get_layers(\*STDIN)] if $proxy_std{stdin};
|
333 | 335 |
$layers{stdout} = [PerlIO::get_layers(\*STDOUT)] if $proxy_std{stdout};
|
334 | 336 |
$layers{stderr} = [PerlIO::get_layers(\*STDERR)] if $proxy_std{stderr};
|
335 | |
_debug( "# post-proxy layers for $_\: @{$layers{$_}}\n" ) for qw/stdin stdout stderr/;
|
|
337 |
# _debug( "# post-proxy layers for $_\: @{$layers{$_}}\n" ) for qw/stdin stdout stderr/;
|
336 | 338 |
# store old handles and setup handles for capture
|
337 | 339 |
$stash->{old} = _copy_std();
|
338 | 340 |
$stash->{new} = { %{$stash->{old}} }; # default to originals
|
339 | 341 |
for ( keys %do ) {
|
340 | 342 |
$stash->{new}{$_} = ($stash->{capture}{$_} ||= File::Temp->new);
|
341 | |
seek $stash->{capture}{$_}, 0, 2;
|
|
343 |
seek( $stash->{capture}{$_}, 0, 2 ) or die "Could not seek on capture handle for $_\n";
|
342 | 344 |
$stash->{pos}{$_} = tell $stash->{capture}{$_};
|
343 | |
_debug("# will capture $_ on " . fileno($stash->{capture}{$_})."\n" );
|
|
345 |
# _debug("# will capture $_ on " . fileno($stash->{capture}{$_})."\n" );
|
344 | 346 |
_start_tee( $_ => $stash ) if $do_tee; # tees may change $stash->{new}
|
345 | 347 |
}
|
346 | 348 |
_wait_for_tees( $stash ) if $do_tee;
|
347 | 349 |
# finalize redirection
|
348 | 350 |
$stash->{new}{stderr} = $stash->{new}{stdout} if $do_merge;
|
349 | |
_debug( "# redirecting in parent ...\n" );
|
|
351 |
# _debug( "# redirecting in parent ...\n" );
|
350 | 352 |
_open_std( $stash->{new} );
|
351 | 353 |
# execute user provided code
|
352 | 354 |
my ($exit_code, $inner_error, $outer_error, @result);
|
353 | 355 |
{
|
354 | 356 |
local *STDIN = *CT_ORIG_STDIN if $localize{stdin}; # get original, not proxy STDIN
|
355 | 357 |
local *STDERR = *STDOUT if $do_merge; # minimize buffer mixups during $code
|
356 | |
_debug( "# finalizing layers ...\n" );
|
|
358 |
# _debug( "# finalizing layers ...\n" );
|
357 | 359 |
_relayer(\*STDOUT, $layers{stdout}) if $do_stdout;
|
358 | 360 |
_relayer(\*STDERR, $layers{stderr}) if $do_stderr;
|
359 | |
_debug( "# running code $code ...\n" );
|
|
361 |
# _debug( "# running code $code ...\n" );
|
360 | 362 |
local $@;
|
361 | 363 |
eval { @result = $code->(); $inner_error = $@ };
|
362 | 364 |
$exit_code = $?; # save this for later
|
363 | 365 |
$outer_error = $@; # save this for later
|
364 | 366 |
}
|
365 | 367 |
# restore prior filehandles and shut down tees
|
366 | |
_debug( "# restoring filehandles ...\n" );
|
|
368 |
# _debug( "# restoring filehandles ...\n" );
|
367 | 369 |
_open_std( $stash->{old} );
|
368 | 370 |
_close( $_ ) for values %{$stash->{old}}; # don't leak fds
|
369 | 371 |
_unproxy( %proxy_std );
|
370 | |
_debug( "# killing tee subprocesses ...\n" ) if $do_tee;
|
|
372 |
# _debug( "# killing tee subprocesses ...\n" ) if $do_tee;
|
371 | 373 |
_kill_tees( $stash ) if $do_tee;
|
372 | 374 |
# return captured output
|
373 | 375 |
my %got;
|
374 | 376 |
for ( keys %do ) {
|
375 | 377 |
_relayer($stash->{capture}{$_}, $layers{$_});
|
376 | 378 |
$got{$_} = _slurp($_, $stash);
|
377 | |
_debug("# slurped " . length($got{$_}) . " bytes from $_\n");
|
|
379 |
# _debug("# slurped " . length($got{$_}) . " bytes from $_\n");
|
378 | 380 |
}
|
379 | 381 |
print CT_ORIG_STDOUT $got{stdout}
|
380 | 382 |
if $do_stdout && $do_tee && $localize{stdout};
|
|
383 | 385 |
$? = $exit_code;
|
384 | 386 |
$@ = $inner_error if $inner_error;
|
385 | 387 |
die $outer_error if $outer_error;
|
386 | |
_debug( "# ending _capture_tee with (@_)...\n" );
|
|
388 |
# _debug( "# ending _capture_tee with (@_)...\n" );
|
387 | 389 |
my @return;
|
388 | 390 |
push @return, $got{stdout} if $do_stdout;
|
389 | 391 |
push @return, $got{stderr} if $do_stderr;
|
|
403 | 405 |
|
404 | 406 |
=head1 VERSION
|
405 | 407 |
|
406 | |
version 0.14
|
|
408 |
version 0.15
|
407 | 409 |
|
408 | 410 |
=head1 SYNOPSIS
|
409 | 411 |
|