sub undo_base {
my ($class, $storeid, $scfg, $volname) = @_;
my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) =
$class->parse_volname($volname);
die "undo_base only possible with base image\n" if !$isBase;
my $vg = $scfg->{vgname};
my $newname = $name;
die "unable to derive VM volume name from '$name'\n"
if $newname !~ s/^base-/vm-/;
die "refusing unsafe target name '$newname'\n"
if $newname !~ m/^vm-$vmid-/;
my $lvs = PVE::Storage::LVMPlugin::lvm_list_volumes($vg);
my $dat = $lvs->{$vg};
die "no such volume group '$vg'\n" if !$dat;
die "no such base volume '$vg/$volname'\n" if !$dat->{$volname};
die "target volume '$vg/$newname' already exists\n" if $dat->{$newname};
my $lvinfo = $dat->{$volname};
die "volume '$vg/$volname' is not an LVM-thin virtual LV\n"
if ($lvinfo->{lv_type} // '') ne 'V';
die "volume '$vg/$volname' is not in configured thinpool '$scfg->{thinpool}'\n"
if ($lvinfo->{pool_lv} // '') ne $scfg->{thinpool};
# A base volume should normally be inactive after create_base().
# If it is active, be conservative: it might be manually opened or in use.
die "refusing to undo active base volume '$vg/$volname'\n"
if ($lvinfo->{lv_state} // '') eq 'a';
# Refuse if there are Proxmox-style snapshots of either the current base name
# or the future vm-* name. The latter avoids confusing stale snapshot names.
my $snap_re = $PVE::JSONSchema::CONFIGID_RE;
foreach my $lv (keys %$dat) {
die "unable to undo base volume - found snapshot '$lv'\n"
if $lv =~ m/^snap_\Q$volname\E_(?:$snap_re)$/;
die "unable to undo base volume - found snapshot using target name '$lv'\n"
if $lv =~ m/^snap_\Q$newname\E_(?:$snap_re)$/;
}
# Refuse if any LVM thin snapshot / linked clone has this base LV as origin.
# This matters because clone_image can create linked clones from base images
# and the file notes that those relationships are not represented in Proxmox
# volume IDs.
my $find_lvm_children = sub {
my ($vg, $origin_lv) = @_;
my $children = [];
my $cmd = [
'/sbin/lvs',
'--noheadings',
'--separator', ':',
'--options', 'lv_name,origin',
$vg,
];
run_command(
$cmd,
outfunc => sub {
my $line = shift;
my ($lv, $origin) = split(/:/, $line, 2);
$lv = trim($lv // '');
$origin = trim($origin // '');
return if !$lv || !$origin;
# Normalize common LVM display variants.
$origin =~ s/^\[//;
$origin =~ s/\]$//;
$origin =~ s!^/dev/\Q$vg\E/!!;
$origin =~ s!^\Q$vg\E/!!;
push @$children, $lv if $origin eq $origin_lv;
},
errmsg => "unable to inspect LVM origins in VG '$vg'",
);
return [ sort @$children ];
};
my $children = $find_lvm_children->($vg, $volname);
die "unable to undo base volume '$vg/$volname' - it is still the origin of: "
. join(', ', @$children) . "\n"
if @$children;
my $renamed = 0;
eval {
my $cmd = ['/sbin/lvrename', $vg, $volname, $newname];
run_command(
$cmd,
errmsg => "lvrename '$vg/$volname' => '$vg/$newname' error",
);
$renamed = 1;
# Reverse create_base(): make it writable and clear activation-skip.
$cmd = ['/sbin/lvchange', '-prw', '-kn', "$vg/$newname"];
run_command(
$cmd,
errmsg => "lvchange '$vg/$newname' read-write/activationskip error",
);
# Match normal Proxmox LVM-thin behavior: do not autoactivate via LVM.
$set_lv_autoactivation->($vg, $newname, 0);
# Normal current VM disks are kept active.
$cmd = ['/sbin/lvchange', '-ay', '-K', "$vg/$newname"];
run_command(
$cmd,
errmsg => "activating LV '$vg/$newname' failed",
);
# Final sanity check using fresh LVM data.
my $post_lvs = PVE::Storage::LVMPlugin::lvm_list_volumes($vg);
my $post_dat = $post_lvs->{$vg} || {};
die "undo_base verification failed - old volume '$vg/$volname' still exists\n"
if $post_dat->{$volname};
die "undo_base verification failed - new volume '$vg/$newname' does not exist\n"
if !$post_dat->{$newname};
die "undo_base verification failed - '$vg/$newname' is not active\n"
if (($post_dat->{$newname}->{lv_state} // '') ne 'a');
};
if (my $err = $@) {
if ($renamed) {
my $rollback_err;
eval {
# Best-effort rollback to the original base state.
run_command(
['/sbin/lvchange', '-an', "$vg/$newname"],
errmsg => "rollback deactivate '$vg/$newname' error",
);
run_command(
['/sbin/lvchange', '-pr', '-ky', "$vg/$newname"],
errmsg => "rollback read-only/activationskip '$vg/$newname' error",
);
run_command(
['/sbin/lvrename', $vg, $newname, $volname],
errmsg => "rollback lvrename '$vg/$newname' => '$vg/$volname' error",
);
};
$rollback_err = $@;
die "undo_base failed: $err\nrollback also failed: $rollback_err"
if $rollback_err;
}
die $err;
}
return $newname;
}