1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
|
=head1 NAME
IPC::Filter - filter data through an external process
=head1 SYNOPSIS
use IPC::Filter qw(filter);
$compressed_data = filter($data, "bzip2");
=head1 DESCRIPTION
The C<filter> function provided by this module passes data through an
external command, thus providing filtering in non-pipeline situations.
=cut
package IPC::Filter;
{ use 5.006; }
use warnings;
use strict;
use Errno 1.00 qw(EPIPE);
use IPC::Open3 1.01 qw(open3);
use IPC::Signal 1.00 qw(sig_name);
use IO::Handle 1.12;
use IO::Poll 0.01 qw(POLLIN POLLOUT POLLERR POLLHUP);
use POSIX qw(_exit);
use Symbol qw(gensym);
our $VERSION = "0.005";
use parent "Exporter";
our @EXPORT_OK = qw(filter);
=head1 FUNCTIONS
=over
=item filter(DATA, SHELL_COMMAND)
=item filter(DATA, PROGRAM, ARGS ...)
The SHELL_COMMAND, or the PROGRAM with ARGS if more arguments are
supplied, is executed as a separate process. (The arguments other
than DATA are ultimately passed to C<exec>; see L<perlfunc(1)/exec>
for explanation of the choice between the two forms.) The DATA (which
must be either a simple string or a reference to a string) is supplied
to the process on its standard input, and the process's standard output
is captured and returned (as a simple string).
If the process exits with a non-zero exit code or on a signal, the
function will C<die>. In the case of a non-zero exit code, the C<die>
message will duplicate the process's standard error output; in any other
case, the error output is discarded.
=cut
my $chunksize = 4096;
sub filter($@) {
my $data = \shift(@_);
if(@_ == 0 || $_[0] eq "-") {
die "filter: invalid command\n";
}
if(ref($data) eq "REF") {
$data = $$data;
}
my $stdin = gensym;
my $stdout = gensym;
my $stderr = gensym;
# Note: perl bug (bug in IPC::Open3 version 1.0106, bug ID
# #32198): if the exec fails in the subprocess created by open3(),
# it uses die() to emit its error message and terminate. If an
# exception handler is installed using eval {}, execution in the
# subprocess continues there instead of the process terminating.
# We avoid nastiness by catching the exception ourselves and
# doing the right thing.
my $parent_pid = $$;
my $child_pid = eval { local $SIG{__DIE__};
open3($stdin, $stdout, $stderr, @_);
};
if($@ ne "") {
my $err = $@;
die $err if $$ == $parent_pid;
print STDERR $err;
_exit 255;
}
local $SIG{PIPE} = "IGNORE";
my $poll = IO::Poll->new;
my $datalen = length($$data);
if($datalen == 0) {
$stdin->close;
} else {
$poll->mask($stdin => POLLOUT | POLLERR | POLLHUP);
}
$poll->mask($stdout => POLLIN | POLLERR | POLLHUP);
$poll->mask($stderr => POLLIN | POLLERR | POLLHUP);
my $datapos = 0;
my @out;
my @err;
while($poll->handles) {
$poll->poll;
if($datapos != $datalen && $poll->events($stdin)) {
my $n = $stdin->syswrite($$data, $chunksize, $datapos);
if(defined $n) {
$datapos += $n;
} elsif($! == EPIPE) {
$datapos = $datalen;
} else {
die "filter: stdin: $!\n";
}
if($datapos == $datalen) {
$poll->remove($stdin);
$stdin->close;
}
}
if($poll->events($stdout)) {
my $output;
unless(defined $stdout->sysread($output, $chunksize)) {
die "filter: stdout: $!\n";
}
if($output eq "") {
$poll->remove($stdout);
} else {
push @out, $output;
}
}
if($poll->events($stderr)) {
my $output;
unless(defined $stderr->sysread($output, $chunksize)) {
die "filter: stderr: $!\n";
}
if($output eq "") {
$poll->remove($stderr);
} else {
push @err, $output;
}
}
}
waitpid $child_pid, 0;
my $status = $?;
if($status == 0) {
return join("", @out);
}
if($status & 127) {
die "filter: process died on SIG".sig_name($status & 127)."\n";
} else {
die join("", "filter: process exited with status ",
$status >> 8, "\n", @err);
}
}
=back
=head1 SEE ALSO
L<IPC::Open2>
=head1 AUTHOR
Andrew Main (Zefram) <zefram@fysh.org>
=head1 COPYRIGHT
Copyright (C) 2004, 2007, 2010, 2011, 2017
Andrew Main (Zefram) <zefram@fysh.org>
=head1 LICENSE
This module is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.
=cut
1;
|