+sub copy_from_vm {
+ my ($file) = @_;
+ return unless $ENV{GRUNTMASTER_COPY_FROM_VM};
+ get_logger->trace("Copying $file from VM");
+ system $ENV{GRUNTMASTER_COPY_FROM_VM}, $file
+}
+
+sub copy_to_vm {
+ my ($file) = @_;
+ return unless $ENV{GRUNTMASTER_COPY_TO_VM};
+ get_logger->trace("Copying $file to VM");
+ system $ENV{GRUNTMASTER_COPY_TO_VM}, $file
+}
+
+sub runvm {
+ my ($name, $arg) = @_;
+ return unless $ENV{GRUNTMASTER_VM};
+ my $cmd = $ENV{GRUNTMASTER_VM} . ' ' . $name;
+ $cmd .= ' ' . $arg if $arg;
+ get_logger->trace("Starting VM $name ($cmd)");
+ $vm{$name} = Expect->new;
+ $vm{$name}->raw_pty(1);
+ $vm{$name}->log_stdout(0);
+ $vm{$name}->spawn($cmd);
+ $vm{$name}->expect(50, '# ') or get_logger->logdie("Error while starting VM $name: ". $vm{$name}->error);
+ $vm{$name}->send("rm -rf ~/work/*\n");
+ $vm{$name}->expect(50, '# ')
+}
+
+sub stopvms {
+ $_->hard_close for values %vm;
+ %vm = %pid = ();
+}
+
+sub execlist_finish {
+ my ($vm, $kill) = @_;
+
+ if ($vm{$vm}) {
+ warn "Cannot kill VM\n" if $kill;
+ $vm{$vm}->expect(50, '# ');
+ } else {
+ kill KILL => $pid{$vm} if $kill;
+ waitpid $pid{$vm}, 0;
+ }
+ write_file "time-stop-$vm", time;
+ return if $kill;
+
+ my $er = "exec-result-$vm";
+ copy_from_vm $er;
+ die "gruntmaster-exec died\n" if -z $er;
+ my ($excode, $exmsg) = read_file $er;
+ unlink $er;
+ chomp ($excode, $exmsg); ## no critic (ProhibitParensWithBuiltins)
+ get_logger->trace("Exec result from $vm: $excode $exmsg");
+ die [$excode, $exmsg] if $excode; ## no critic (RequireCarping)
+}
+
+sub execlist {
+ my ($vm, @args) = @_;
+ write_file "time-start-$vm", time;
+ my $er = "exec-result-$vm";
+ if ($vm{$vm}) {
+ my $cmd = ">$er " . shell_quote 'gruntmaster-exec', @args;
+ get_logger->trace("Running in VM $vm: $cmd");
+ $vm{$vm}->send($cmd, "\n");
+ } else {
+ $pid{$vm} = fork // die "Cannot fork\n";
+ unless ($pid{$vm}) {
+ open STDOUT, '>', $er or die "Cannot open $er\n";
+ get_logger->trace("Running: gruntmaster-exec @args");
+ exec 'gruntmaster-exec', @args;
+ }