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   3366 use strict;
  1         1  
  1         35  
10 1     1   4 use warnings;
  1         2  
  1         22  
11 1     1   5 use English;
  1         2  
  1         6  
12 1     1   439 use Proc::SafeExec;
  1         3  
  1         39  
13 1     1   5 use File::Temp;
  1         2  
  1         115  
14 1     1   7 use Try::Tiny;
  1         1  
  1         38  
15 1     1   451 use Template;
  1         17774  
  1         34  
16              
17 1     1   583 use Data::Dumper;
  1         5465  
  1         94  
18              
19 1     1   575 use Moose;
  1         404687  
  1         6  
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 5630 my $self = shift;
64              
65 17         247 my @args = $self->_build_path( shift );
66 17         365 my $template = Template->new(
67             {
68             });
69              
70             # compose a list of command arguments
71 17         33582 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         36 my @cmd_args;
78 17         34 foreach my $item (@{$self->args()}) {
  17         694  
79 28         66 my $value;
80 28 50       167 $template->process(\$item, $template_vars, \$value) || die "Error processing argument template.";
81 28         63477 push @cmd_args, $value;
82             }
83              
84 17         40 my %filehandles;
85              
86             my @feed_to_stdin;
87 17 100       704 if (defined $self->stdin()) {
88 6         47 my @raw_stdin_data;
89 6 100       180 if (ref $self->stdin() eq '') {
    50          
90 3         86 push @raw_stdin_data, $self->stdin();
91             } elsif (ref $self->stdin() eq 'ARRAY') {
92 3         7 push @raw_stdin_data, @{$self->stdin()};
  3         81  
93             }
94 6         24 foreach my $line (@raw_stdin_data) {
95 9         22 my $value;
96 9 50       36 $template->process(\$line, $template_vars, \$value) || die "Error processing stdin template.";
97 9         13277 push @feed_to_stdin, $value;
98             }
99              
100             # we have data to pipe to stdin, create a filehandle
101 6         24 $filehandles{stdin} = 'new';
102             }
103              
104 17 100       546 if (defined $self->env()) {
105 8 50       226 if (ref $self->env() eq 'HASH') {
106 8         16 foreach my $key (keys %{$self->env()}) {
  8         194  
107 8         16 my $value;
108 8 50       226 $template->process(\$self->env()->{$key}, $template_vars, \$value) || die "Error processing environment template.";
109 8         11621 $ENV{$key} = $value;
110             }
111             }
112             }
113              
114 17         343 my $stdout = File::Temp->new();
115 17         10956 $filehandles{stdout} = \*$stdout;
116              
117 17         55 my $stderr = File::Temp->new();
118 17         5288 $filehandles{stderr} = \*$stderr;
119              
120              
121             # compose the system command to execute
122 17         51 my @cmd;
123 17         59 push @cmd, $self->{LOCATION};
124 17         34 push @cmd, @cmd_args;
125              
126 17         225 my $command = Proc::SafeExec->new(
127             {
128             exec => \@cmd,
129             %filehandles,
130             });
131             try {
132 17     17   4525 local $SIG{ALRM} = sub { die "alarm\n" };
  1         2000247  
133 17 100       112 if (scalar @feed_to_stdin) {
134 6         168 my $stdin = $command->stdin();
135 6         97 print $stdin join("\n", @feed_to_stdin);
136             }
137 17         2049 alarm $self->timeout();
138 17         178 $command->wait();
139             } catch {
140 1 50   1   62 if ($_ eq "alarm\n") {
141 1         99 die "System command timed out after " . $self->timeout() . " seconds";
142             }
143 0         0 die $_;
144             } finally {
145 17     17   1053557 alarm 0;
146 17         99109 };
147              
148 16         376 my $stderr_content = do {
149 16         406 open my $fh, '<', $stderr->filename;
150 16         1233 local $INPUT_RECORD_SEPARATOR;
151 16         2036 <$fh>;
152             };
153              
154 16 100       205 if ($command->exit_status() != 0) {
155 1         29 die "System command exited with return code " . ($command->exit_status() >> 8) . ". STDERR: $stderr_content";
156             }
157              
158 15         244 my $stdout_content = do {
159 15         171 open my $fh, '<', $stdout->filename;
160 15         628 local $INPUT_RECORD_SEPARATOR;
161 15         563 <$fh>;
162             };
163              
164 15 50       987 if ($self->chomp_output()) {
165 15         53 chomp $stdout_content;
166             }
167              
168 15         168 return $stdout_content;
169             }
170              
171             sub get_meta {
172 2     2 1 2644 my $self = shift;
173              
174             # If we have no path, we tell the caller that we are a connector
175 2         29 my @path = $self->_build_path( shift );
176 2 100       14 if (scalar @path == 0) {
177 1         22 return { TYPE => "connector" };
178             }
179              
180 1         15 return {TYPE => "scalar" };
181             }
182              
183             sub exists {
184              
185 3     3 1 11 my $self = shift;
186              
187             # No path = connector root which always exists
188 3         26 my @path = $self->_build_path( shift );
189 3 100       10 if (scalar @path == 0) {
190 1         22 return 1;
191             }
192 2         4 my $val;
193 2         9 eval {
194 2         19 $val = $self->get( \@path );
195             };
196 2         1687 return defined $val;
197              
198             }
199              
200 1     1   7096 no Moose;
  1         2  
  1         5  
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