]> git.proxmox.com Git - pve-installer.git/commitdiff
factor out command execution helpers
authorThomas Lamprecht <t.lamprecht@proxmox.com>
Mon, 3 Apr 2023 14:32:37 +0000 (16:32 +0200)
committerThomas Lamprecht <t.lamprecht@proxmox.com>
Fri, 9 Jun 2023 07:36:58 +0000 (09:36 +0200)
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
Proxmox/Makefile
Proxmox/Sys/Command.pm [new file with mode: 0644]
proxinstall

index ee10bc2282a37cc55b0d4bc2831ac9de3fe09842..31c9eff49fae476bb507ef758f80f25149de297d 100644 (file)
@@ -8,6 +8,7 @@ PERL5DIR=$(DESTDIR)/usr/share/perl5/Proxmox
 PERL_MODULES=\
     Install/Setup.pm \
     Log.pm \
+    Sys/Command.pm \
 
 .PHONY: install
 install: $(PERL_MODULES)
diff --git a/Proxmox/Sys/Command.pm b/Proxmox/Sys/Command.pm
new file mode 100644 (file)
index 0000000..c48827d
--- /dev/null
@@ -0,0 +1,161 @@
+package Proxmox::Sys::Command;
+
+use strict;
+use warnings;
+
+use Gtk3 qw(); # FIXME: drop once possible (when (G)UI plugin approacg is there)
+use IO::File;
+use IPC::Open3;
+use IO::Select;
+use String::ShellQuote;
+
+use Proxmox::Install::Setup;
+use Proxmox::Log;
+
+use base qw(Exporter);
+our @EXPORT_OK = qw(run_command syscmd);
+
+my sub shellquote {
+    my $str = shift;
+    return String::ShellQuote::shell_quote($str);
+}
+
+my sub cmd2string {
+    my ($cmd) = @_;
+
+    die "no arguments" if !$cmd;
+    return $cmd if !ref($cmd);
+
+    my $quoted_args = [ map { shellquote($_) } $cmd->@* ];
+
+    return join (' ', $quoted_args->@*);
+}
+
+sub syscmd {
+    my ($cmd) = @_;
+
+    return run_command($cmd, undef, undef, 1);
+}
+
+sub run_command {
+    my ($cmd, $func, $input, $noout) = @_;
+
+    my $cmdstr;
+    if (!ref($cmd)) {
+       $cmdstr = $cmd;
+       if ($cmd =~ m/|/) {
+           # see 'man bash' for option pipefail
+           $cmd = [ '/bin/bash', '-c', "set -o pipefail && $cmd" ];
+       } else {
+           $cmd = [ $cmd ];
+       }
+    } else {
+       $cmdstr = cmd2string($cmd);
+    }
+
+    my $cmdtxt;
+    if ($input && ($cmdstr !~ m/chpasswd/)) {
+       $cmdtxt = "# $cmdstr <<EOD\n$input";
+       chomp $cmdtxt;
+       $cmdtxt .= "\nEOD\n";
+    } else {
+       $cmdtxt = "# $cmdstr\n";
+    }
+
+    if (is_test_mode()) {
+       print $cmdtxt;
+       STDOUT->flush();
+    }
+    log_info($cmdtxt);
+
+    my ($reader, $writer, $error) = (IO::File->new(), IO::File->new(), IO::File->new());
+
+    my $orig_pid = $$;
+
+    my $pid = eval { open3($writer, $reader, $error, @$cmd) || die $!; };
+    my $err = $@;
+
+    if ($orig_pid != $$) { # catch exec errors
+       POSIX::_exit (1);
+       kill ('KILL', $$);
+    }
+    die $err if $err;
+
+    print $writer $input if defined $input;
+    close $writer;
+
+    my $select = IO::Select->new();
+    $select->add($reader);
+    $select->add($error);
+
+    my ($ostream, $logout) = ('', '', '');
+
+    while ($select->count) {
+       my @handles = $select->can_read (0.2);
+
+       Gtk3::main_iteration() while Gtk3::events_pending();
+
+       next if !scalar (@handles); # timeout
+
+       foreach my $h (@handles) {
+           my $buf = '';
+           my $count = sysread ($h, $buf, 4096);
+           if (!defined ($count)) {
+               my $err = $!;
+               kill (9, $pid);
+               waitpid ($pid, 0);
+               die "command '$cmd' failed: $err";
+           }
+           $select->remove($h) if !$count;
+           if ($h eq $reader) {
+               $ostream .= $buf if !($noout || $func);
+               $logout .= $buf;
+               while ($logout =~ s/^([^\010\r\n]*)(\r|\n|(\010)+|\r\n)//s) {
+                   my $line = $1;
+                   $func->($line) if $func;
+               }
+
+           } elsif ($h eq $error) {
+               $ostream .= $buf if !($noout || $func);
+           }
+           print $buf;
+           STDOUT->flush();
+           log_info($buf);
+       }
+    }
+
+    &$func($logout) if $func;
+
+    my $rv = waitpid ($pid, 0);
+
+    return $? if $noout; # behave like standard system();
+
+    if ($? == -1) {
+       die "command '$cmdstr' failed to execute\n";
+    } elsif (my $sig = ($? & 127)) {
+       die "command '$cmdstr' failed - got signal $sig\n";
+    } elsif (my $exitcode = ($? >> 8)) {
+       die "command '$cmdstr' failed with exit code $exitcode";
+    }
+
+    return $ostream;
+}
+
+# forks and runs the provided coderef in the child
+# do not use syscmd or run_command as both confuse the GTK mainloop if
+# run from a child process
+sub run_in_background {
+    my ($cmd) = @_;
+
+    my $pid = fork() // die "fork failed: $!\n";
+    if (!$pid) {
+       eval { $cmd->(); };
+       if (my $err = $@) {
+           warn "run_in_background error: $err\n";
+           POSIX::_exit(1);
+       }
+       POSIX::_exit(0);
+    }
+}
+
+1;
index 416483f486af6abf56c3f2e6969e5e6e5c998a23..a12a66a334f4bd0dd9b75ff830ad9db974854f2c 100755 (executable)
@@ -8,16 +8,12 @@ $ENV{LC_ALL} = 'C';
 
 use Getopt::Long;
 use IPC::Open2;
-use IPC::Open3;
 use IO::File;
-use IO::Select;
 use Cwd 'abs_path';
 use Glib;
 use Gtk3 '-init';
 use Gtk3::WebKit2;
 use Encode;
-use String::ShellQuote;
-use Data::Dumper;
 use File::Basename;
 use File::Path;
 use Time::HiRes;
@@ -25,6 +21,7 @@ use POSIX ":sys_wait_h";
 
 use Proxmox::Install::Setup;
 use Proxmox::Log;
+use Proxmox::Sys::Command qw(run_command syscmd);
 
 if (!$ENV{G_SLICE} ||  $ENV{G_SLICE} ne "always-malloc") {
     die "do not use slice allocator (run with 'G_SLICE=always-malloc ./proxinstall ...')\n";
@@ -337,156 +334,6 @@ compatibility_level = 2
 
 _EOD
 
-sub shellquote {
-    my $str = shift;
-
-    return String::ShellQuote::shell_quote($str);
-}
-
-sub cmd2string {
-    my ($cmd) = @_;
-
-    die "no arguments" if !$cmd;
-
-    return $cmd if !ref($cmd);
-
-    my @qa = ();
-    foreach my $arg (@$cmd) { push @qa, shellquote($arg); }
-
-    return join (' ', @qa);
-}
-
-sub syscmd {
-    my ($cmd) = @_;
-
-    return run_command($cmd, undef, undef, 1);
-}
-
-sub run_command {
-    my ($cmd, $func, $input, $noout) = @_;
-
-    my $cmdstr;
-    if (!ref($cmd)) {
-       $cmdstr = $cmd;
-       if ($cmd =~ m/|/) {
-           # see 'man bash' for option pipefail
-           $cmd = [ '/bin/bash', '-c', "set -o pipefail && $cmd" ];
-       } else {
-           $cmd = [ $cmd ];
-       }
-    } else {
-       $cmdstr = cmd2string($cmd);
-    }
-
-    my $cmdtxt;
-    if ($input && ($cmdstr !~ m/chpasswd/)) {
-       $cmdtxt = "# $cmdstr <<EOD\n$input";
-       chomp $cmdtxt;
-       $cmdtxt .= "\nEOD\n";
-    } else {
-       $cmdtxt = "# $cmdstr\n";
-    }
-
-    if (is_test_mode()) {
-       print $cmdtxt;
-       STDOUT->flush();
-    }
-
-    log_cmd($cmdtxt);
-
-    my $reader = IO::File->new();
-    my $writer = IO::File->new();
-    my $error  = IO::File->new();
-
-    my $orig_pid = $$;
-
-    my $pid = eval { open3($writer, $reader, $error, @$cmd) || die $!; };
-    my $err = $@;
-
-    # catch exec errors
-    if ($orig_pid != $$) {
-       POSIX::_exit (1);
-       kill ('KILL', $$);
-    }
-
-    die $err if $err;
-
-    print $writer $input if defined $input;
-    close $writer;
-
-    my $select = IO::Select->new();
-    $select->add($reader);
-    $select->add($error);
-
-    my ($ostream, $logout) = ('', '', '');
-
-    while ($select->count) {
-       my @handles = $select->can_read (0.2);
-
-       Gtk3::main_iteration() while Gtk3::events_pending();
-
-       next if !scalar (@handles); # timeout
-
-       foreach my $h (@handles) {
-           my $buf = '';
-           my $count = sysread ($h, $buf, 4096);
-           if (!defined ($count)) {
-               my $err = $!;
-               kill (9, $pid);
-               waitpid ($pid, 0);
-               die "command '$cmd' failed: $err";
-           }
-           $select->remove($h) if !$count;
-           if ($h eq $reader) {
-               $ostream .= $buf if !($noout || $func);
-               $logout .= $buf;
-               while ($logout =~ s/^([^\010\r\n]*)(\r|\n|(\010)+|\r\n)//s) {
-                   my $line = $1;
-                   &$func($line) if $func;
-               }
-
-           } elsif ($h eq $error) {
-               $ostream .= $buf if !($noout || $func);
-           }
-           print $buf;
-           STDOUT->flush();
-           log_cmd($buf);
-       }
-    }
-
-    &$func($logout) if $func;
-
-    my $rv = waitpid ($pid, 0);
-
-    return $? if $noout; # behave like standard system();
-
-    if ($? == -1) {
-       die "command '$cmdstr' failed to execute\n";
-    } elsif (my $sig = ($? & 127)) {
-       die "command '$cmdstr' failed - got signal $sig\n";
-    } elsif (my $exitcode = ($? >> 8)) {
-       die "command '$cmdstr' failed with exit code $exitcode";
-    }
-
-    return $ostream;
-}
-
-# forks and runs the provided coderef in the child
-# do not use syscmd or run_command as both confuse the GTK mainloop if
-# run from a child process
-sub run_in_background {
-    my ($cmd) = @_;
-
-    my $pid = fork() // die "fork failed: $!\n";
-    if (!$pid) {
-       eval { $cmd->(); };
-       if (my $err = $@) {
-           warn "run_in_background error: $err\n";
-           POSIX::_exit(1);
-       }
-       POSIX::_exit(0);
-    }
-}
 
 sub detect_country {
 
@@ -2849,7 +2696,7 @@ sub create_country_view {
                };
                $kbd_config =~ s/^\s+//gm;
 
-               run_in_background( sub {
+               Proxmox::Sys::Command::run_in_background(sub {
                    write_config($kbd_config, '/etc/default/keyboard');
                    system("setupcon");
                });