use strict;
use warnings;
+
use IO::File;
+use Net::IP;
use POSIX;
-use PVE::Tools qw(run_command);
-use PVE::Storage::Plugin;
+
+use PVE::ProcFSTools;
use PVE::RPCEnvironment;
-use Net::IP;
+use PVE::Storage::Plugin;
+use PVE::Tools qw(run_command);
use base qw(PVE::Storage::Plugin);
# static zfs helper methods
-sub zfs_parse_size {
- my ($text) = @_;
-
- return 0 if !$text;
-
- if ($text =~ m/^(\d+(\.\d+)?)([TGMK])?$/) {
-
- my ($size, $reminder, $unit) = ($1, $2, $3);
-
- if ($unit) {
- if ($unit eq 'K') {
- $size *= 1024;
- } elsif ($unit eq 'M') {
- $size *= 1024*1024;
- } elsif ($unit eq 'G') {
- $size *= 1024*1024*1024;
- } elsif ($unit eq 'T') {
- $size *= 1024*1024*1024*1024;
- } else {
- die "got unknown zfs size unit '$unit'\n";
- }
- }
-
- if ($reminder) {
- $size = ceil($size);
- }
-
- return $size;
-
- }
-
- warn "unable to parse zfs size '$text'\n";
-
- return 0;
-}
-
sub zfs_parse_zvol_list {
my ($text) = @_;
if ($refquota eq 'none') {
$zvol->{size} = 0;
} else {
- $zvol->{size} = zfs_parse_size($refquota);
+ $zvol->{size} = $refquota + 0;
}
$zvol->{format} = 'subvol';
} else {
- $zvol->{size} = zfs_parse_size($size);
+ $zvol->{size} = $size + 0;
$zvol->{format} = 'raw';
}
if ($origin !~ /^-$/) {
my $cfg_mountpoint = $scfg->{mountpoint};
# ignore failure, pool might currently not be imported
- my $mountpoint = eval {
- $class->zfs_get_properties($scfg, 'mountpoint', $scfg->{pool}, 1)
+ my $mountpoint;
+ eval {
+ my $res = $class->zfs_get_properties($scfg, 'mountpoint', $scfg->{pool}, 1);
+ $mountpoint = PVE::Storage::Plugin::verify_path($res, 1) if defined($res);
};
if (defined($cfg_mountpoint)) {
} else {
$scfg->{mountpoint} = $mountpoint;
}
+
+ return;
}
sub path {
my ($class, $scfg, $volname, $size) = @_;
my $dataset = "$scfg->{pool}/$volname";
+ my $quota = $size ? "${size}k" : "none";
my $cmd = ['create', '-o', 'acltype=posixacl', '-o', 'xattr=sa',
- '-o', "refquota=${size}k", $dataset];
+ '-o', "refquota=${quota}", $dataset];
$class->zfs_request($scfg, undef, @$cmd);
}
sub zfs_list_zvol {
my ($class, $scfg) = @_;
- my $text = $class->zfs_request($scfg, 10, 'list', '-o', 'name,volsize,origin,type,refquota', '-t', 'volume,filesystem', '-Hr');
+ my $text = $class->zfs_request($scfg, 10, 'list', '-o', 'name,volsize,origin,type,refquota', '-t', 'volume,filesystem', '-Hrp');
my $zvols = zfs_parse_zvol_list($text);
return undef if !$zvols;
sub volume_snapshot_rollback {
my ($class, $scfg, $storeid, $volname, $snap) = @_;
- my $vname = ($class->parse_volname($volname))[1];
+ my (undef, $vname, undef, undef, undef, undef, $format) = $class->parse_volname($volname);
+
+ my $msg = $class->zfs_request($scfg, undef, 'rollback', "$scfg->{pool}/$vname\@$snap");
+
+ # we have to unmount rollbacked subvols, to invalidate wrong kernel
+ # caches, they get mounted in activate volume again
+ # see zfs bug #10931 https://github.com/openzfs/zfs/issues/10931
+ if ($format eq 'subvol') {
+ eval { $class->zfs_request($scfg, undef, 'unmount', "$scfg->{pool}/$vname"); };
+ if (my $err = $@) {
+ die $err if $err !~ m/not currently mounted$/;
+ }
+ }
- $class->zfs_request($scfg, undef, 'rollback', "$scfg->{pool}/$vname\@$snap");
+ return $msg;
}
sub volume_rollback_is_possible {
my ($class, $scfg, $storeid, $volname, $snap) = @_;
my $recentsnap = $class->zfs_get_latest_snapshot($scfg, $volname);
- if ($snap ne $recentsnap) {
- die "can't rollback, more recent snapshots exist\n";
- }
+
+ die "can't rollback, no snapshots exist at all\n"
+ if !defined($recentsnap);
+
+ die "can't rollback, '$snap' is not most recent snapshot\n"
+ if $snap ne $recentsnap;
return 1;
}
return $snaps;
}
+my sub dataset_mounted_heuristic {
+ my ($dataset) = @_;
+
+ my $mounts = PVE::ProcFSTools::parse_proc_mounts();
+ for my $mp (@$mounts) {
+ my ($what, $dir, $fs) = $mp->@*;
+ next if $fs ne 'zfs';
+ # check for root-dataset or any child-dataset (root-dataset could have 'canmount=off')
+ # If any child is mounted heuristically assume that `zfs mount -a` was successful
+ next if $what !~ m!^$dataset(?:/|$)!;
+ return 1;
+ }
+ return 0;
+}
+
sub activate_storage {
my ($class, $storeid, $scfg, $cache) = @_;
# Note: $scfg->{pool} can include dataset <pool>/<dataset>
- my $pool = $scfg->{pool};
- $pool =~ s!/.*$!!;
+ my $dataset = $scfg->{pool};
+ my $pool = ($dataset =~ s!/.*$!!r);
+
+ return 1 if dataset_mounted_heuristic($dataset); # early return
my $pool_imported = sub {
- my @param = ('-o', 'name', '-H', "$pool");
+ my @param = ('-o', 'name', '-H', $pool);
my $res = eval { $class->zfs_request($scfg, undef, 'zpool_list', @param) };
- if ($@) {
- warn "$@\n";
- return undef;
- }
+ warn "$@\n" if $@;
+
return defined($res) && $res =~ m/$pool/;
};
if (!$pool_imported->()) {
# import can only be done if not yet imported!
- my @param = ('-d', '/dev/disk/by-id/', '-o', 'cachefile=none', "$pool");
+ my @param = ('-d', '/dev/disk/by-id/', '-o', 'cachefile=none', $pool);
eval { $class->zfs_request($scfg, undef, 'zpool_import', @param) };
if (my $err = $@) {
# just could've raced with another import, so recheck if it is imported
- die "could not activate storage '$storeid', $@\n" if !$pool_imported->();
+ die "could not activate storage '$storeid', $err\n" if !$pool_imported->();
}
}
+ eval { $class->zfs_request($scfg, undef, 'mount', '-a') };
+ die "could not activate storage '$storeid', $@\n" if $@;
return 1;
}
return 1 if defined($snapname);
- my (undef, undef, undef, undef, undef, undef, $format) = $class->parse_volname($volname);
-
- return 1 if $format ne 'raw';
+ my (undef, $dataset, undef, undef, undef, undef, $format) = $class->parse_volname($volname);
- $class->zfs_wait_for_zvol_link($scfg, $volname);
+ if ($format eq 'raw') {
+ $class->zfs_wait_for_zvol_link($scfg, $volname);
+ } elsif ($format eq 'subvol') {
+ my $mounted = $class->zfs_get_properties($scfg, 'mounted', "$scfg->{pool}/$dataset");
+ if ($mounted !~ m/^yes$/) {
+ $class->zfs_request($scfg, undef, 'mount', "$scfg->{pool}/$dataset");
+ }
+ }
return 1;
}
my $name = $class->find_free_diskname($storeid, $scfg, $vmid, $format);
if ($format eq 'subvol') {
- my $size = $class->zfs_request($scfg, undef, 'list', '-H', '-o', 'refquota', "$scfg->{pool}/$basename");
+ my $size = $class->zfs_request($scfg, undef, 'list', '-Hp', '-o', 'refquota', "$scfg->{pool}/$basename");
chomp($size);
$class->zfs_request($scfg, undef, 'clone', "$scfg->{pool}/$basename\@$snap", "$scfg->{pool}/$name", '-o', "refquota=$size");
} else {
}
sub volume_import {
- my ($class, $scfg, $storeid, $fh, $volname, $format, $base_snapshot, $with_snapshots) = @_;
+ my ($class, $scfg, $storeid, $fh, $volname, $format, $snapshot, $base_snapshot, $with_snapshots, $allow_rename) = @_;
die "unsupported import stream format for $class: $format\n"
if $format ne 'zfs';
die "internal error: invalid file handle for volume_import\n"
if !defined($fd);
- my $dataset = ($class->parse_volname($volname))[1];
+ my (undef, $dataset, $vmid) = $class->parse_volname($volname);
my $zfspath = "$scfg->{pool}/$dataset";
my $suffix = defined($base_snapshot) ? "\@$base_snapshot" : '';
my $exists = 0 == run_command(['zfs', 'get', '-H', 'name', $zfspath.$suffix],
- noerr => 1, errfunc => sub {});
+ noerr => 1, quiet => 1);
if (defined($base_snapshot)) {
die "base snapshot '$zfspath\@$base_snapshot' doesn't exist\n" if !$exists;
- } else {
- die "volume '$zfspath' already exists\n" if $exists;
+ } elsif ($exists) {
+ die "volume '$zfspath' already exists\n" if !$allow_rename;
+ warn "volume '$zfspath' already exists - importing with a different name\n";
+ $dataset = $class->find_free_diskname($storeid, $scfg, $vmid, $format);
+ $zfspath = "$scfg->{pool}/$dataset";
}
eval { run_command(['zfs', 'recv', '-F', '--', $zfspath], input => "<&$fd") };
die $err;
}
- return;
+ return "$storeid:$dataset";
}
sub volume_import_formats {
- my ($class, $scfg, $storeid, $volname, $base_snapshot, $with_snapshots) = @_;
+ my ($class, $scfg, $storeid, $volname, $snapshot, $base_snapshot, $with_snapshots) = @_;
- return $class->volume_export_formats($scfg, $storeid, $volname, undef, $base_snapshot, $with_snapshots);
+ return $class->volume_export_formats($scfg, $storeid, $volname, $snapshot, $base_snapshot, $with_snapshots);
}
1;