File Coverage

blib/lib/IPC/Pipeline.pm
Criterion Covered Total %
statement 56 58 96.5
branch 30 40 75.0
condition n/a
subroutine 8 8 100.0
pod 0 2 0.0
total 94 108 87.0


line stmt bran cond sub pod time code
1             package IPC::Pipeline;
2              
3             # Copyright (c) 2012, cPanel, Inc.
4             # All rights reserved.
5             # http://cpanel.net/
6             #
7             # This is free software; you can redistribute it and/or modify it under the same
8             # terms as Perl itself. See the LICENSE file for further details.
9              
10 17     17   593339 use strict;
  17         44  
  17         573  
11 17     17   84 use warnings;
  17         31  
  17         381  
12              
13 17     17   15218 use POSIX ();
  17         170634  
  17         559  
14              
15             BEGIN {
16 17     17   156 use Exporter ();
  17         31  
  17         338  
17 17     17   87 use vars qw( $VERSION @ISA @EXPORT );
  17         37  
  17         1738  
18              
19 17     17   45 $VERSION = '0.9';
20 17         276 @ISA = ('Exporter');
21 17         20744 @EXPORT = ('pipeline');
22             }
23              
24             sub exec_filter {
25 13     13 0 220 my ($filter) = @_;
26              
27 13 100       687 if ( ref($filter) eq 'CODE' ) {
    50          
28 5         210 exit $filter->();
29             }
30             elsif ( ref($filter) eq 'ARRAY' ) {
31 8 0       0 exec(@$filter) or die("Cannot exec(): $!");
32             }
33              
34 0         0 die('Invalid filter');
35             }
36              
37             sub pipeline {
38 31     31 0 2789313 my @filters = @_[ 3 .. $#_ ];
39              
40 31 100       197 die('Not enough arguments') unless @_ >= 4;
41              
42             #
43             # Validate the filters and die early.
44             #
45 27         103 foreach my $filter (@filters) {
46 48 100       615 next if ref($filter) =~ /^CODE|ARRAY$/;
47              
48 1         7 die('Filter passed is not a CODE reference or ARRAY containing command and arguments');
49             }
50              
51             #
52             # Create the initial pipe for passing data into standard input to the first
53             # filter passed. Share a single pipe for standard error use for each
54             # process.
55             #
56 26 50       1818 pipe my ( $child_out, $in ) or die("Cannot create a file handle pair for standard input piping: $!");
57 26 50       765 pipe my ( $error_out, $error_in ) or die("Cannot create a file handle pair for standard error piping: $!");
58              
59 26         59 my @pids;
60              
61 26         79 foreach my $filter (@filters) {
62 42 50       1546 pipe my ( $out, $child_in ) or die("Cannot create a file handle pair for standard output piping: $!");
63              
64 42         63099 my $pid = fork();
65              
66 42 50       2817 if ( !defined($pid) ) {
    100          
67 0         0 die("Cannot fork(): $!");
68             }
69             elsif ( $pid == 0 ) {
70 13 50       4640 open( STDIN, '<&=' . fileno($child_out) ) or die("Cannot dup2() last output fd to current child stdin: $!");
71 13 50       1048 open( STDOUT, '>&=' . fileno($child_in) ) or die("Cannot dup2() last input fd to current child stdout: $!");
72 13 50       823 open( STDERR, '>&=' . fileno($error_in) ) or die("Cannot dup2() error pipe input to current child stderr: $!");
73              
74 13         1200 exec_filter($filter);
75             }
76              
77             #
78             # This last child STDOUT file handle should be duplicated onto the next
79             # process' standard input reader, or will be passed as the last child
80             # output file descriptor if no other subsequent commands are left
81             # to be run.
82             #
83 29         868 $child_out = $out;
84              
85 29         4038 push @pids, $pid;
86             }
87              
88             #
89             # Substitute the first three arguments passed by the user with the file
90             # handle on the parent's writing end of the initial pipe created for
91             # writing to the first command, the last output file handle for the
92             # last command, and the standard error handle. If typeglobs or numeric
93             # file descriptors for existing file handles are passed, an attempt will
94             # be made to dup2() them as appropriate.
95             #
96             # The evals around the assignments are present so that we don't die when the
97             # argument passed in is undef. Perl 5.19 and newer don't like that.
98             # [perl #7508, #109726]
99 13 100       675 if ( !defined $_[0] ) {
    100          
100 9         290 eval { $_[0] = $in };
  9         173  
101             }
102             elsif ( ref( $_[0] ) eq 'GLOB' ) {
103 3         537 open( $_[0], '>&=' . fileno($in) );
104             }
105             else {
106 1         43 POSIX::dup2( fileno($in), $_[0] );
107             }
108              
109 13 100       319 if ( !defined $_[1] ) {
    100          
110 9         39 eval { $_[1] = $child_out };
  9         120  
111             }
112             elsif ( ref( $_[1] ) eq 'GLOB' ) {
113 3         129 open( $_[1], '<&=' . fileno($child_out) );
114             }
115             else {
116 1         28 POSIX::dup2( fileno($child_out), $_[1] );
117             }
118              
119 13 100       493 if ( !defined $_[2] ) {
    100          
120 9         79 eval { $_[2] = $error_out };
  9         161  
121             }
122             elsif ( ref( $_[2] ) eq 'GLOB' ) {
123 3         111 open( $_[2], '<&=' . fileno($error_out) );
124             }
125             else {
126 1         11 POSIX::dup2( fileno($error_out), $_[2] );
127             }
128              
129 13 100       1292 return wantarray ? @pids : \@pids;
130             }
131              
132             1;
133              
134             __END__