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) 2014, 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   420210 use strict;
  17         34  
  17         522  
11 17     17   81 use warnings;
  17         30  
  17         382  
12              
13 17     17   13994 use POSIX ();
  17         113346  
  17         554  
14              
15             BEGIN {
16 17     17   125 use Exporter ();
  17         34  
  17         324  
17 17     17   77 use vars qw( $VERSION @ISA @EXPORT );
  17         23  
  17         1966  
18              
19 17     17   40 our $VERSION = '1.0_0001';
20 17         264 our @ISA = ('Exporter');
21 17         11325 our @EXPORT = ('pipeline');
22             }
23              
24             sub exec_filter {
25 13     13 0 229 my ($filter) = @_;
26              
27 13 100       465 if ( ref($filter) eq 'CODE' ) {
    50          
28 5         198 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 2073959 my @filters = @_[ 3 .. $#_ ];
39              
40 31 100       171 die('Not enough arguments') unless @_ >= 4;
41              
42             #
43             # Validate the filters and die early.
44             #
45 27         87 foreach my $filter (@filters) {
46 48 100       564 next if ref($filter) =~ /^CODE|ARRAY$/;
47              
48 1         6 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       1176 pipe my ( $child_out, $in ) or die("Cannot create a file handle pair for standard input piping: $!");
57 26 50       795 pipe my ( $error_out, $error_in ) or die("Cannot create a file handle pair for standard error piping: $!");
58              
59 26         69 my @pids;
60              
61 26         68 foreach my $filter (@filters) {
62 42 50       1653 pipe my ( $out, $child_in ) or die("Cannot create a file handle pair for standard output piping: $!");
63              
64 42         48839 my $pid = fork();
65              
66 42 50       4720 if ( !defined($pid) ) {
    100          
67 0         0 die("Cannot fork(): $!");
68             }
69             elsif ( $pid == 0 ) {
70 13 50       4902 open( STDIN, '<&=' . fileno($child_out) ) or die("Cannot dup2() last output fd to current child stdin: $!");
71 13 50       915 open( STDOUT, '>&=' . fileno($child_in) ) or die("Cannot dup2() last input fd to current child stdout: $!");
72 13 50       1091 open( STDERR, '>&=' . fileno($error_in) ) or die("Cannot dup2() error pipe input to current child stderr: $!");
73              
74 13         673 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         664 $child_out = $out;
84              
85 29         3750 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       703 if ( !defined $_[0] ) {
    100          
100 9         212 eval { $_[0] = $in };
  9         224  
101             }
102             elsif ( ref( $_[0] ) eq 'GLOB' ) {
103 3         189 open( $_[0], '>&=' . fileno($in) );
104             }
105             else {
106 1         54 POSIX::dup2( fileno($in), $_[0] );
107             }
108              
109 13 100       319 if ( !defined $_[1] ) {
    100          
110 9         150 eval { $_[1] = $child_out };
  9         244  
111             }
112             elsif ( ref( $_[1] ) eq 'GLOB' ) {
113 3         111 open( $_[1], '<&=' . fileno($child_out) );
114             }
115             else {
116 1         30 POSIX::dup2( fileno($child_out), $_[1] );
117             }
118              
119 13 100       155 if ( !defined $_[2] ) {
    100          
120 9         182 eval { $_[2] = $error_out };
  9         187  
121             }
122             elsif ( ref( $_[2] ) eq 'GLOB' ) {
123 3         93 open( $_[2], '<&=' . fileno($error_out) );
124             }
125             else {
126 1         23 POSIX::dup2( fileno($error_out), $_[2] );
127             }
128              
129 13 100       1252 return wantarray ? @pids : \@pids;
130             }
131              
132             1;
133              
134             __END__