5
0
mirror of git://git.proxmox.com/git/pve-guest-common.git synced 2025-03-12 20:58:20 +03:00
pve-guest-common/PVE/GuestHelpers.pm
Thomas Lamprecht 43c899e407 fix config_with_pending_array for falsy current values
one could have a config with:
> acpi: 0

and a pending deletion for that to restore the default 1 value.

The config_with_pending_array method then pushed the key twice, one
in the loop iterating the config itself correctly and once in the
pending delete hash, which is normally only for those options not yet
referenced in the config at all. Here the check was on "truthiness"
not definedness, fix that.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2020-05-20 17:04:40 +02:00

244 lines
6.0 KiB
Perl

package PVE::GuestHelpers;
use strict;
use warnings;
use PVE::Tools;
use PVE::Storage;
use POSIX qw(strftime);
use Scalar::Util qw(weaken);
use base qw(Exporter);
our @EXPORT_OK = qw(safe_string_ne safe_boolean_ne safe_num_ne typesafe_ne);
# We use a separate lock to block migration while a replication job
# is running.
our $lockdir = '/var/lock/pve-manager';
# safe variable comparison functions
sub safe_num_ne {
my ($a, $b) = @_;
return 0 if !defined($a) && !defined($b);
return 1 if !defined($a);
return 1 if !defined($b);
return $a != $b;
}
sub safe_string_ne {
my ($a, $b) = @_;
return 0 if !defined($a) && !defined($b);
return 1 if !defined($a);
return 1 if !defined($b);
return $a ne $b;
}
sub safe_boolean_ne {
my ($a, $b) = @_;
# we don't check if value is defined, since undefined
# is false (so it's a valid boolean)
# negate both values to normalize and compare
return !$a != !$b;
}
sub typesafe_ne {
my ($a, $b, $type) = @_;
return 0 if !defined($a) && !defined($b);
return 1 if !defined($a);
return 1 if !defined($b);
if ($type eq 'string') {
return safe_string_ne($a, $b);
} elsif ($type eq 'number' || $type eq 'integer') {
return safe_num_ne($a, $b);
} elsif ($type eq 'boolean') {
return safe_boolean_ne($a, $b);
}
die "internal error: can't compare $a and $b with type $type";
}
sub guest_migration_lock {
my ($vmid, $timeout, $func, @param) = @_;
my $lockid = "pve-migrate-$vmid";
mkdir $lockdir;
my $res = PVE::Tools::lock_file("$lockdir/$lockid", $timeout, $func, @param);
die $@ if $@;
return $res;
}
sub check_hookscript {
my ($volid, $storecfg) = @_;
$storecfg = PVE::Storage::config() if !defined($storecfg);
my ($path, undef, $type) = PVE::Storage::path($storecfg, $volid);
die "'$volid' is not in the snippets directory\n"
if $type ne 'snippets';
die "script '$volid' does not exist\n"
if ! -f $path;
die "script '$volid' is not executable\n"
if ! -x $path;
return $path;
}
sub exec_hookscript {
my ($conf, $vmid, $phase, $stop_on_error) = @_;
return if !$conf->{hookscript};
eval {
my $hookscript = check_hookscript($conf->{hookscript});
die $@ if $@;
PVE::Tools::run_command([$hookscript, $vmid, $phase]);
};
if (my $err = $@) {
my $errmsg = "hookscript error for $vmid on $phase: $err\n";
die $errmsg if ($stop_on_error);
warn $errmsg;
}
}
# takes a snapshot list (e.g., qm/pct snapshot_list API call result) and
# prints it out in a nice tree sorted by age. Can cope with multiple roots
sub print_snapshot_tree {
my ($snapshot_list) = @_;
my $snapshots = { map { $_->{name} => $_ } @$snapshot_list };
my @roots;
foreach my $e (@$snapshot_list) {
my $parent;
if (($parent = $e->{parent}) && defined $snapshots->{$parent}) {
push @{$snapshots->{$parent}->{children}}, $e->{name};
} else {
push @roots, $e->{name};
}
}
# sort the elements by snaptime - with "current" (no snaptime) highest
my $snaptimesort = sub {
return +1 if !defined $snapshots->{$a}->{snaptime};
return -1 if !defined $snapshots->{$b}->{snaptime};
return $snapshots->{$a}->{snaptime} <=> $snapshots->{$b}->{snaptime};
};
# recursion function for displaying the tree
my $snapshottree_weak;
$snapshottree_weak = sub {
my ($prefix, $root, $snapshots) = @_;
my $e = $snapshots->{$root};
my $description = $e->{description} || 'no-description';
($description) = $description =~ m/(.*)$/m;
my $timestring = "";
if (defined $e->{snaptime}) {
$timestring = strftime("%F %H:%M:%S", localtime($e->{snaptime}));
}
my $len = 30 - length($prefix); # for aligning the description
printf("%s %-${len}s %-23s %s\n", $prefix, $root, $timestring, $description);
if ($e->{children}) {
$prefix = " $prefix";
foreach my $child (sort $snaptimesort @{$e->{children}}) {
$snapshottree_weak->($prefix, $child, $snapshots);
}
}
};
my $snapshottree = $snapshottree_weak;
weaken($snapshottree_weak);
foreach my $root (sort $snaptimesort @roots) {
$snapshottree->('`->', $root, $snapshots);
}
}
sub format_pending {
my ($data) = @_;
foreach my $item (sort { $a->{key} cmp $b->{key}} @$data) {
my $k = $item->{key};
next if $k eq 'digest';
my $v = $item->{value};
my $p = $item->{pending};
if ($k eq 'description') {
$v = PVE::Tools::encode_text($v) if defined($v);
$p = PVE::Tools::encode_text($p) if defined($p);
}
if (defined($v)) {
if ($item->{delete}) {
print "del $k: $v\n";
} elsif (defined($p)) {
print "cur $k: $v\n";
print "new $k: $p\n";
} else {
print "cur $k: $v\n";
}
} elsif (defined($p)) {
print "new $k: $p\n";
}
}
}
# returns the config as an array of hashes, each hash can have the following keys:
# key (the config property name, non-optional)
# value (the current value in effect - if any)
# pending (a new, still pending, value - if any)
# delete (when deletions are pending, this is set to either 2 (force) or 1 (graceful))
sub config_with_pending_array {
my ($conf, $pending_delete_hash) = @_;
my $res = [];
foreach my $opt (keys %$conf) {
next if ref($conf->{$opt});
my $item = { key => $opt };
$item->{value} = $conf->{$opt} if defined($conf->{$opt});
$item->{pending} = $conf->{pending}->{$opt} if defined($conf->{pending}->{$opt});
$item->{delete} = ($pending_delete_hash->{$opt}->{force} ? 2 : 1) if exists $pending_delete_hash->{$opt};
push @$res, $item;
}
foreach my $opt (keys %{$conf->{pending}}) {
next if $opt eq 'delete';
next if ref($conf->{pending}->{$opt}); # just to be sure
next if defined($conf->{$opt});
my $item = { key => $opt };
$item->{pending} = $conf->{pending}->{$opt};
push @$res, $item;
}
while (my ($opt, $force) = each %$pending_delete_hash) {
next if defined($conf->{pending}->{$opt});
next if defined($conf->{$opt});
my $item = { key => $opt, delete => ($force ? 2 : 1)};
push @$res, $item;
}
return $res;
}
1;