]> git.proxmox.com Git - pve-storage.git/blobdiff - PVE/Storage.pm
add BTRFS storage plugin
[pve-storage.git] / PVE / Storage.pm
index bd6e15e9f1f9f05dec9a6cbcd88391705ff08212..a36842bc19fc0c6f09b4128ca8b2dd3a28e2f509 100755 (executable)
@@ -37,8 +37,8 @@ use PVE::Storage::ISCSIDirectPlugin;
 use PVE::Storage::GlusterfsPlugin;
 use PVE::Storage::ZFSPoolPlugin;
 use PVE::Storage::ZFSPlugin;
-use PVE::Storage::DRBDPlugin;
 use PVE::Storage::PBSPlugin;
+use PVE::Storage::BTRFSPlugin;
 
 # Storage API version. Increment it on changes in storage API interface.
 use constant APIVER => 8;
@@ -60,8 +60,8 @@ PVE::Storage::ISCSIDirectPlugin->register();
 PVE::Storage::GlusterfsPlugin->register();
 PVE::Storage::ZFSPoolPlugin->register();
 PVE::Storage::ZFSPlugin->register();
-PVE::Storage::DRBDPlugin->register();
 PVE::Storage::PBSPlugin->register();
+PVE::Storage::BTRFSPlugin->register();
 
 # load third-party plugins
 if ( -d '/usr/share/perl5/PVE/Storage/Custom' ) {
@@ -75,10 +75,8 @@ if ( -d '/usr/share/perl5/PVE/Storage/Custom' ) {
            require $file;
 
            # Check perl interface:
-           die "not derived from PVE::Storage::Plugin\n"
-               if !$modname->isa('PVE::Storage::Plugin');
-           die "does not provide an api() method\n"
-               if !$modname->can('api');
+           die "not derived from PVE::Storage::Plugin\n" if !$modname->isa('PVE::Storage::Plugin');
+           die "does not provide an api() method\n" if !$modname->can('api');
            # Check storage API version and that file is really storage plugin.
            my $version = $modname->api();
            die "implements an API version newer than current ($version > " . APIVER . ")\n"
@@ -86,11 +84,11 @@ if ( -d '/usr/share/perl5/PVE/Storage/Custom' ) {
            my $min_version = (APIVER - APIAGE);
            die "API version too old, please update the plugin ($version < $min_version)\n"
                if $version < $min_version;
+           # all OK, do import and register (i.e., "use")
            import $file;
            $modname->register();
 
-           # If we got this far and the API version is not the same, make some
-           # noise:
+           # If we got this far and the API version is not the same, make some noise:
            warn "Plugin \"$modname\" is implementing an older storage API, an upgrade is recommended\n"
                if $version != APIVER;
        };
@@ -103,10 +101,10 @@ if ( -d '/usr/share/perl5/PVE/Storage/Custom' ) {
 # initialize all plugins
 PVE::Storage::Plugin->init();
 
-my $UDEVADM = '/sbin/udevadm';
-
 our $iso_extension_re = qr/\.(?:iso|img)/i;
 
+our $vztmpl_extension_re = qr/\.tar\.([gx]z)/i;
+
 #  PVE::Storage utility functions
 
 sub config {
@@ -129,6 +127,28 @@ sub lock_storage_config {
     }
 }
 
+# FIXME remove maxfiles for PVE 8.0 or PVE 9.0
+my $convert_maxfiles_to_prune_backups = sub {
+    my ($scfg) = @_;
+
+    return if !$scfg;
+
+    my $maxfiles = delete $scfg->{maxfiles};
+
+    if (!defined($scfg->{'prune-backups'}) && defined($maxfiles)) {
+       my $prune_backups;
+       if ($maxfiles) {
+           $prune_backups = { 'keep-last' => $maxfiles };
+       } else { # maxfiles 0 means no limit
+           $prune_backups = { 'keep-all' => 1 };
+       }
+       $scfg->{'prune-backups'} = PVE::JSONSchema::print_property_string(
+           $prune_backups,
+           'prune-backups'
+       );
+    }
+};
+
 sub storage_config {
     my ($cfg, $storeid, $noerr) = @_;
 
@@ -138,6 +158,8 @@ sub storage_config {
 
     die "storage '$storeid' does not exist\n" if (!$noerr && !$scfg);
 
+    $convert_maxfiles_to_prune_backups->($scfg);
+
     return $scfg;
 }
 
@@ -172,7 +194,7 @@ sub storage_check_enabled {
 
 # storage_can_replicate:
 # return true if storage supports replication
-# (volumes alocated with vdisk_alloc() has replication feature)
+# (volumes allocated with vdisk_alloc() has replication feature)
 sub storage_can_replicate {
     my ($cfg, $storeid, $format) = @_;
 
@@ -193,6 +215,26 @@ sub file_size_info {
     return PVE::Storage::Plugin::file_size_info($filename, $timeout);
 }
 
+sub get_volume_notes {
+    my ($cfg, $volid, $timeout) = @_;
+
+    my ($storeid, $volname) = parse_volume_id($volid);
+    my $scfg = storage_config($cfg, $storeid);
+    my $plugin = PVE::Storage::Plugin->lookup($scfg->{type});
+
+    return $plugin->get_volume_notes($scfg, $storeid, $volname, $timeout);
+}
+
+sub update_volume_notes {
+    my ($cfg, $volid, $notes, $timeout) = @_;
+
+    my ($storeid, $volname) = parse_volume_id($volid);
+    my $scfg = storage_config($cfg, $storeid);
+    my $plugin = PVE::Storage::Plugin->lookup($scfg->{type});
+
+    $plugin->update_volume_notes($scfg, $storeid, $volname, $notes, $timeout);
+}
+
 sub volume_size_info {
     my ($cfg, $volid, $timeout) = @_;
 
@@ -454,8 +496,15 @@ sub check_volume_access {
     return undef;
 }
 
-my $volume_is_base_and_used__no_lock = sub {
-    my ($scfg, $storeid, $plugin, $volname) = @_;
+# NOTE: this check does not work for LVM-thin, where the clone -> base
+# reference is not encoded in the volume ID.
+# see note in PVE::Storage::LvmThinPlugin for details.
+sub volume_is_base_and_used {
+    my ($cfg, $volid) = @_;
+
+    my ($storeid, $volname) = parse_volume_id($volid);
+    my $scfg = storage_config($cfg, $storeid);
+    my $plugin = PVE::Storage::Plugin->lookup($scfg->{type});
 
     my ($vtype, $name, $vmid, undef, undef, $isBase, undef) =
        $plugin->parse_volname($volname);
@@ -478,21 +527,6 @@ my $volume_is_base_and_used__no_lock = sub {
        }
     }
     return 0;
-};
-
-# NOTE: this check does not work for LVM-thin, where the clone -> base
-# reference is not encoded in the volume ID.
-# see note in PVE::Storage::LvmThinPlugin for details.
-sub volume_is_base_and_used {
-    my ($cfg, $volid) = @_;
-
-    my ($storeid, $volname) = parse_volume_id($volid);
-    my $scfg = storage_config($cfg, $storeid);
-    my $plugin = PVE::Storage::Plugin->lookup($scfg->{type});
-
-    $plugin->cluster_lock_storage($storeid, $scfg->{shared}, undef, sub {
-       return &$volume_is_base_and_used__no_lock($scfg, $storeid, $plugin, $volname);
-    });
 }
 
 # try to map a filesystem path to a volume identifier
@@ -543,7 +577,7 @@ sub path_to_volume_id {
        } elsif ($path =~ m!^$isodir/([^/]+$iso_extension_re)$!) {
            my $name = $1;
            return ('iso', "$sid:iso/$name");
-       } elsif ($path =~ m!^$tmpldir/([^/]+\.tar\.gz)$!) {
+       } elsif ($path =~ m!^$tmpldir/([^/]+$vztmpl_extension_re)$!) {
            my $name = $1;
            return ('vztmpl', "$sid:vztmpl/$name");
        } elsif ($path =~ m!^$privatedir/(\d+)$!) {
@@ -575,22 +609,22 @@ sub path {
 }
 
 sub abs_filesystem_path {
-    my ($cfg, $volid) = @_;
+    my ($cfg, $volid, $allow_blockdev) = @_;
 
     my $path;
     if (parse_volume_id ($volid, 1)) {
        activate_volumes($cfg, [ $volid ]);
        $path = PVE::Storage::path($cfg, $volid);
     } else {
-       if (-f $volid) {
+       if (-f $volid || ($allow_blockdev && -b $volid)) {
            my $abspath = abs_path($volid);
            if ($abspath && $abspath =~ m|^(/.+)$|) {
                $path = $1; # untaint any path
            }
        }
     }
-
-    die "can't find file '$volid'\n" if !($path && -f $path);
+    die "can't find file '$volid'\n"
+       if !($path && (-f $path || ($allow_blockdev && -b $path)));
 
     return $path;
 }
@@ -724,8 +758,14 @@ sub storage_migrate {
                or die "receive command failed: $!\n";
            close($input);
 
-           my ($ip) = <$info> =~ /^($PVE::Tools::IPRE)$/ or die "no tunnel IP received\n";
-           my ($port) = <$info> =~ /^(\d+)$/ or die "no tunnel port received\n";
+           my $try_ip = <$info> // '';
+           my ($ip) = $try_ip =~ /^($PVE::Tools::IPRE)$/ # untaint
+               or die "no tunnel IP received, got '$try_ip'\n";
+
+           my $try_port = <$info> // '';
+           my ($port) = $try_port =~ /^(\d+)$/ # untaint
+               or die "no tunnel port received, got '$try_port'\n";
+
            my $socket = IO::Socket::IP->new(PeerHost => $ip, PeerPort => $port, Type => SOCK_STREAM)
                or die "failed to connect to tunnel at $ip:$port\n";
            # we won't be reading from the socket
@@ -876,7 +916,7 @@ sub vdisk_free {
     $plugin->cluster_lock_storage($storeid, $scfg->{shared}, undef, sub {
        # LVM-thin allows deletion of still referenced base volumes!
        die "base volume '$volname' is still in use by linked clones\n"
-           if &$volume_is_base_and_used__no_lock($scfg, $storeid, $plugin, $volname);
+           if volume_is_base_and_used($cfg, $volid);
 
        my (undef, undef, undef, undef, undef, $isBase, $format) =
            $plugin->parse_volname($volname);
@@ -892,13 +932,13 @@ sub vdisk_free {
 }
 
 sub vdisk_list {
-    my ($cfg, $storeid, $vmid, $vollist) = @_;
+    my ($cfg, $storeid, $vmid, $vollist, $ctype) = @_;
 
     my $ids = $cfg->{ids};
 
     storage_check_enabled($cfg, $storeid) if ($storeid);
 
-    my $res = {};
+    my $res = $storeid ? { $storeid => [] } : {};
 
     # prepare/activate/refresh all storages
 
@@ -915,6 +955,7 @@ sub vdisk_list {
            next if $storeid && $storeid ne $sid;
            next if !storage_check_enabled($cfg, $sid, undef, 1);
            my $content = $ids->{$sid}->{content};
+           next if defined($ctype) && !$content->{$ctype};
            next if !($content->{rootdir} || $content->{images});
            push @$storage_list, $sid;
        }
@@ -924,9 +965,8 @@ sub vdisk_list {
 
     activate_storage_list($cfg, $storage_list, $cache);
 
-    foreach my $sid (keys %$ids) {
+    for my $sid ($storage_list->@*) {
        next if $storeid && $storeid ne $sid;
-       next if !storage_check_enabled($cfg, $sid, undef, 1);
 
        my $scfg = $ids->{$sid};
        my $plugin = PVE::Storage::Plugin->lookup($scfg->{type});
@@ -1031,8 +1071,7 @@ sub activate_storage {
 
     # only call udevsettle if there are events
     if ($newseq > $cache->{uevent_seqnum}) {
-       my $timeout = 30;
-       system ("$UDEVADM settle --timeout=$timeout"); # ignore errors
+       system ("udevadm settle --timeout=30"); # ignore errors
        $cache->{uevent_seqnum} = $newseq;
     }
 
@@ -1411,7 +1450,8 @@ sub decompressor_info {
     die "ERROR: archive format not defined\n"
        if !defined($decompressor->{$format});
 
-    my $decomp = $decompressor->{$format}->{$comp} if $comp;
+    my $decomp;
+    $decomp = $decompressor->{$format}->{$comp} if $comp;
 
     my $info = {
        format => $format,
@@ -1586,6 +1626,8 @@ sub prune_backups {
        $keep = PVE::JSONSchema::parse_property_string('prune-backups', $scfg->{'prune-backups'});
     }
 
+    activate_storage($cfg, $storeid);
+
     my $plugin = PVE::Storage::Plugin->lookup($scfg->{type});
     return $plugin->prune_backups($scfg, $storeid, $keep, $vmid, $type, $dryrun, $logfunc);
 }
@@ -1601,13 +1643,14 @@ my $prune_mark = sub {
     foreach my $prune_entry (@{$prune_entries}) {
        my $mark = $prune_entry->{mark};
        my $id = $id_func->($prune_entry->{ctime});
+       $already_included->{$id} = 1 if defined($mark) && $mark eq 'keep';
+    }
 
-       next if $already_included->{$id};
+    foreach my $prune_entry (@{$prune_entries}) {
+       my $mark = $prune_entry->{mark};
+       my $id = $id_func->($prune_entry->{ctime});
 
-       if (defined($mark)) {
-           $already_included->{$id} = 1 if $mark eq 'keep';
-           next;
-       }
+       next if defined($mark) || $already_included->{$id};
 
        if (!$newly_included->{$id}) {
            last if scalar(keys %{$newly_included}) >= $keep_count;
@@ -1622,7 +1665,10 @@ my $prune_mark = sub {
 sub prune_mark_backup_group {
     my ($backup_group, $keep) = @_;
 
-    if (!scalar(grep {$_ > 0} values %{$keep})) {
+    my $keep_all = delete $keep->{'keep-all'};
+
+    if ($keep_all || !scalar(grep {$_ > 0} values %{$keep})) {
+       $keep = { 'keep-all' => 1 } if $keep_all;
        foreach my $prune_entry (@{$backup_group}) {
            $prune_entry->{mark} = 'keep';
        }
@@ -1648,8 +1694,8 @@ sub prune_mark_backup_group {
     $prune_mark->($prune_list, $keep->{'keep-weekly'}, sub {
        my ($ctime) = @_;
        my ($sec, $min, $hour, $day, $month, $year) = localtime($ctime);
-       my $iso_week = int(strftime("%V", $sec, $min, $hour, $day, $month - 1, $year - 1900));
-       my $iso_week_year = int(strftime("%G", $sec, $min, $hour, $day, $month - 1, $year - 1900));
+       my $iso_week = int(strftime("%V", $sec, $min, $hour, $day, $month, $year));
+       my $iso_week_year = int(strftime("%G", $sec, $min, $hour, $day, $month, $year));
        return "$iso_week/$iso_week_year";
     });
     $prune_mark->($prune_list, $keep->{'keep-monthly'}, sub {
@@ -1804,7 +1850,7 @@ sub get_bandwidth_limit {
     my ($operation, $storage_list, $override) = @_;
 
     # called for each limit (global, per-storage) with the 'default' and the
-    # $operation limit and should udpate $override for every limit affecting
+    # $operation limit and should update $override for every limit affecting
     # us.
     my $use_global_limits = 0;
     my $apply_limit = sub {