]>
git.proxmox.com Git - pve-installer.git/blob - Proxmox/Sys/Command.pm
1 package Proxmox
::Sys
::Command
;
10 use String
::ShellQuote
;
11 use POSIX
":sys_wait_h";
13 use Proxmox
::Install
::ISOEnv
;
17 use base
qw(Exporter);
18 our @EXPORT_OK = qw(run_command syscmd);
22 return String
::ShellQuote
::shell_quote
($str);
28 die "no arguments" if !$cmd;
29 return $cmd if !ref($cmd);
31 my $quoted_args = [ map { shellquote
($_) } $cmd->@* ];
33 return join (' ', $quoted_args->@*);
36 # Safely for the (sub-)process specified by $pid to exit, using a timeout.
38 # When kill => 1 is set, at first a TERM-signal is sent to the process before
39 # checking if it exited.
40 # If that fails, KILL is sent to process and then up to timeout => $timeout
41 # seconds (default: 5) are waited for the process to exit.
43 # On sucess, the exitcode of the process is returned, otherwise `undef` (aka.
44 # the process was unkillable).
45 my sub wait_for_process
{
46 my ($pid, %params) = @_;
48 kill('TERM', $pid) if $params{kill};
50 my $terminated = waitpid($pid, WNOHANG
);
51 return $? if $terminated > 0;
53 kill('KILL', $pid) if $params{kill};
55 my $timeout = $params{timeout
} // 5;
57 $terminated = waitpid($pid, WNOHANG
);
58 return $? if $terminated > 0;
62 log_warn
("failed to kill child pid $pid, probably stuck in D-state?\n");
64 # We tried our best, better let the child hang in the back then completely
65 # blocking installer progress .. it's a rather short-lived environment anyway
71 return run_command
($cmd, undef, undef, 1);
74 # Runs a command an a subprocess, properly handling IO via piping, cleaning up and passing back the
77 # If $cmd contains a pipe |, the command will be executed inside a bash shell.
78 # If $cmd contains 'chpasswd', the input will be specially quoted for that purpose.
81 # * $cmd - The command to run, either a single string or array with individual arguments
82 # * $func - Logging subroutine to call, receives both stdout and stderr
83 # * $input - Stdin contents for the spawned subprocess
84 # * $noout - Whether to append any process output to the return value
86 my ($cmd, $func, $input, $noout) = @_;
92 # see 'man bash' for option pipefail
93 $cmd = [ '/bin/bash', '-c', "set -o pipefail && $cmd" ];
98 $cmdstr = cmd2string
($cmd);
102 if ($input && ($cmdstr !~ m/chpasswd/)) {
103 $cmdtxt = "# $cmdstr <<EOD\n$input";
105 $cmdtxt .= "\nEOD\n";
107 $cmdtxt = "# $cmdstr\n";
110 if (is_test_mode
()) {
116 my ($reader, $writer, $error) = (IO
::File-
>new(), IO
::File-
>new(), IO
::File-
>new());
120 my $pid = eval { open3
($writer, $reader, $error, @$cmd) || die $!; };
123 if ($orig_pid != $$) { # catch exec errors
129 print $writer $input if defined $input;
132 my $select = IO
::Select-
>new();
133 $select->add($reader);
134 $select->add($error);
136 my ($ostream, $logout) = ('', '', '');
139 while ($select->count) {
140 my @handles = $select->can_read (0.2);
142 # If we catch a signal, stop processing & clean up
148 Proxmox
::UI
::process_events
();
150 next if !scalar (@handles); # timeout
152 foreach my $h (@handles) {
154 my $count = sysread ($h, $buf, 4096);
155 if (!defined ($count)) {
157 wait_for_process
($pid, kill => 1);
158 die "command '$cmd' failed: $err";
160 $select->remove($h) if !$count;
162 $ostream .= $buf if !($noout || $func);
164 while ($logout =~ s/^([^\010\r\n]*)(\r|\n|(\010)+|\r\n)//s) {
166 $func->($line) if $func;
169 } elsif ($h eq $error) {
170 $ostream .= $buf if !($noout || $func);
178 &$func($logout) if $func;
180 my $ec = wait_for_process
($pid, kill => $caught_sig);
182 # behave like standard system(); returns -1 in case of errors too
183 return ($ec // -1) if $noout;
186 # Don't fail completely here to let the install continue
187 warn "command '$cmdstr' failed to exit properly\n";
188 } elsif ($ec == -1) {
189 croak
"command '$cmdstr' failed to execute\n";
190 } elsif (my $sig = ($ec & 127)) {
191 croak
"command '$cmdstr' failed - got signal $sig\n";
192 } elsif (my $exitcode = ($ec >> 8)) {
193 croak
"command '$cmdstr' failed with exit code $exitcode";
199 # forks and runs the provided coderef in the child
200 # do not use syscmd or run_command as both confuse the GTK mainloop if
201 # run from a child process
202 sub run_in_background
{
205 my $pid = fork() // die "fork failed: $!\n";
209 warn "run_in_background error: $err\n";