File Coverage

blib/lib/Connector/Proxy/Proc/SafeExec.pm
Criterion Covered Total %
statement 104 109 95.4
branch 21 30 70.0
condition n/a
subroutine 16 17 94.1
pod 3 3 100.0
total 144 159 90.5


line stmt bran cond sub pod time code
1             # Connector::Proxy::Proc::SafeExec
2             #
3             # Connector class for running system commands
4             #
5             # Written by Martin Bartosch for the OpenXPKI project 2012
6             #
7             package Connector::Proxy::Proc::SafeExec;
8              
9 1     1   3503 use strict;
  1         3  
  1         29  
10 1     1   5 use warnings;
  1         2  
  1         24  
11 1     1   6 use English;
  1         2  
  1         7  
12 1     1   393 use Proc::SafeExec;
  1         2  
  1         29  
13 1     1   6 use File::Temp;
  1         2  
  1         84  
14 1     1   7 use Try::Tiny;
  1         2  
  1         51  
15 1     1   485 use Template;
  1         19502  
  1         37  
16              
17 1     1   630 use Data::Dumper;
  1         6268  
  1         61  
18              
19 1     1   564 use Moose;
  1         457953  
  1         5  
20             extends 'Connector::Proxy';
21              
22             has args => (
23             is => 'rw',
24             isa => 'ArrayRef[Str]',
25             default => sub { [] },
26             );
27              
28             has timeout => (
29             is => 'rw',
30             isa => 'Int',
31             default => 5,
32             );
33              
34             has chomp_output => (
35             is => 'rw',
36             isa => 'Bool',
37             default => 1,
38             );
39              
40             has stdin => (
41             is => 'rw',
42             isa => 'Str|ArrayRef[Str]|Undef',
43             );
44              
45             has env => (
46             is => 'rw',
47             isa => 'HashRef[Str]',
48             );
49              
50             sub _build_config {
51 0     0   0 my $self = shift;
52              
53 0 0       0 if (! -x $self->LOCATION()) {
54 0         0 die("Specified system command is not executable: " . $self->LOCATION());
55             }
56              
57 0         0 return 1;
58             }
59              
60             # this method always returns the file contents, regardless of the specified
61             # key
62             sub get {
63 17     17 1 6190 my $self = shift;
64              
65 17         292 my @args = $self->_build_path( shift );
66 17         473 my $template = Template->new(
67             {
68             });
69              
70             # compose a list of command arguments
71 17         39909 my $template_vars = {
72             ARGS => \@args,
73             };
74              
75             # process configured system command arguments and replace templates
76             # in it with the passed arguments, accessible via [% ARGS.0 %]
77 17         51 my @cmd_args;
78 17         33 foreach my $item (@{$self->args()}) {
  17         830  
79 28         75 my $value;
80 28 50       184 $template->process(\$item, $template_vars, \$value) || die "Error processing argument template.";
81 28         73661 push @cmd_args, $value;
82             }
83              
84 17         47 my %filehandles;
85              
86             my @feed_to_stdin;
87 17 100       822 if (defined $self->stdin()) {
88 6         46 my @raw_stdin_data;
89 6 100       242 if (ref $self->stdin() eq '') {
    50          
90 3         99 push @raw_stdin_data, $self->stdin();
91             } elsif (ref $self->stdin() eq 'ARRAY') {
92 3         13 push @raw_stdin_data, @{$self->stdin()};
  3         115  
93             }
94 6         27 foreach my $line (@raw_stdin_data) {
95 9         18 my $value;
96 9 50       47 $template->process(\$line, $template_vars, \$value) || die "Error processing stdin template.";
97 9         14803 push @feed_to_stdin, $value;
98             }
99              
100             # we have data to pipe to stdin, create a filehandle
101 6         42 $filehandles{stdin} = 'new';
102             }
103              
104 17 100       752 if (defined $self->env()) {
105 8 50       245 if (ref $self->env() eq 'HASH') {
106 8         25 foreach my $key (keys %{$self->env()}) {
  8         228  
107 8         22 my $value;
108 8 50       251 $template->process(\$self->env()->{$key}, $template_vars, \$value) || die "Error processing environment template.";
109 8         12653 $ENV{$key} = $value;
110             }
111             }
112             }
113              
114 17         323 my $stdout = File::Temp->new();
115 17         10675 $filehandles{stdout} = \*$stdout;
116              
117 17         71 my $stderr = File::Temp->new();
118 17         6590 $filehandles{stderr} = \*$stderr;
119              
120              
121             # compose the system command to execute
122 17         61 my @cmd;
123 17         69 push @cmd, $self->{LOCATION};
124 17         54 push @cmd, @cmd_args;
125              
126 17         181 my $command = Proc::SafeExec->new(
127             {
128             exec => \@cmd,
129             %filehandles,
130             });
131             try {
132 17     17   5223 local $SIG{ALRM} = sub { die "alarm\n" };
  1         2000464  
133 17 100       109 if (scalar @feed_to_stdin) {
134 6         604 my $stdin = $command->stdin();
135 6         100 print $stdin join("\n", @feed_to_stdin);
136             }
137 17         2211 alarm $self->timeout();
138 17         204 $command->wait();
139             } catch {
140 1 50   1   62 if ($_ eq "alarm\n") {
141 1         92 die "System command timed out after " . $self->timeout() . " seconds";
142             }
143 0         0 die $_;
144             } finally {
145 17     17   1076641 alarm 0;
146 17         94931 };
147              
148 16         391 my $stderr_content = do {
149 16         507 open my $fh, '<', $stderr->filename;
150 16         1573 local $INPUT_RECORD_SEPARATOR;
151 16         1199 <$fh>;
152             };
153              
154 16 100       155 if ($command->exit_status() != 0) {
155 1         38 die "System command exited with return code " . ($command->exit_status() >> 8) . ". STDERR: $stderr_content";
156             }
157              
158 15         353 my $stdout_content = do {
159 15         214 open my $fh, '<', $stdout->filename;
160 15         793 local $INPUT_RECORD_SEPARATOR;
161 15         717 <$fh>;
162             };
163              
164 15 50       1072 if ($self->chomp_output()) {
165 15         74 chomp $stdout_content;
166             }
167              
168 15         163 return $stdout_content;
169             }
170              
171             sub get_meta {
172 2     2 1 3221 my $self = shift;
173              
174             # If we have no path, we tell the caller that we are a connector
175 2         42 my @path = $self->_build_path( shift );
176 2 100       34 if (scalar @path == 0) {
177 1         30 return { TYPE => "connector" };
178             }
179              
180 1         18 return {TYPE => "scalar" };
181             }
182              
183             sub exists {
184              
185 3     3 1 26 my $self = shift;
186              
187             # No path = connector root which always exists
188 3         36 my @path = $self->_build_path( shift );
189 3 100       22 if (scalar @path == 0) {
190 1         37 return 1;
191             }
192 2         19 my $val;
193 2         12 eval {
194 2         15 $val = $self->get( \@path );
195             };
196 2         1977 return defined $val;
197              
198             }
199              
200 1     1   8612 no Moose;
  1         10  
  1         6  
201             __PACKAGE__->meta->make_immutable;
202              
203             1;
204             __END__
205              
206             =head1 Name
207              
208             Connector::Builtin::System::Exec
209              
210             =head1 Description
211