]> git.proxmox.com Git - pve-storage.git/blobdiff - PVE/Storage/DirPlugin.pm
plugin: change name, separator and error message for dir overrides
[pve-storage.git] / PVE / Storage / DirPlugin.pm
index 39f3b30e103c6e23cdc1fa4bc58d135d02c81692..9e305f52c79b823c2233c91112c7f41045887ef6 100644 (file)
@@ -2,8 +2,13 @@ package PVE::Storage::DirPlugin;
 
 use strict;
 use warnings;
+
 use Cwd;
+use Encode qw(decode encode);
 use File::Path;
+use IO::File;
+use POSIX;
+
 use PVE::Storage::Plugin;
 use PVE::JSONSchema qw(get_standard_option);
 
@@ -17,11 +22,11 @@ sub type {
 
 sub plugindata {
     return {
-       content => [ { images => 1, rootdir => 1, vztmpl => 1, iso => 1, backup => 1, none => 1 },
+       content => [ { images => 1, rootdir => 1, vztmpl => 1, iso => 1, backup => 1, snippets => 1, none => 1 },
                     { images => 1,  rootdir => 1 }],
        format => [ { raw => 1, qcow2 => 1, vmdk => 1, subvol => 1 } , 'raw' ],
     };
-}   
+}
 
 sub properties {
     return {
@@ -36,25 +41,32 @@ sub properties {
        },
        is_mountpoint => {
            description =>
-               "Assume the directory is an externally managed mountpoint. " .
-               "If nothing is mounted the storage will be considered offline.",
-           type => 'boolean',
+               "Assume the given path is an externally managed mountpoint " .
+               "and consider the storage offline if it is not mounted. ".
+               "Using a boolean (yes/no) value serves as a shortcut to using the target path in this field.",
+           type => 'string',
            default => 'no',
        },
+       bwlimit => get_standard_option('bwlimit'),
     };
 }
 
 sub options {
     return {
        path => { fixed => 1 },
-        nodes => { optional => 1 },
+       'content-dirs' => { optional => 1 },
+       nodes => { optional => 1 },
        shared => { optional => 1 },
        disable => { optional => 1 },
-        maxfiles => { optional => 1 },
+       maxfiles => { optional => 1 },
+       'prune-backups' => { optional => 1 },
+       'max-protected-backups' => { optional => 1 },
        content => { optional => 1 },
        format => { optional => 1 },
        mkdir => { optional => 1 },
        is_mountpoint => { optional => 1 },
+       bwlimit => { optional => 1 },
+       preallocation => { optional => 1 },
    };
 }
 
@@ -73,15 +85,125 @@ sub path_is_mounted {
     return undef;
 }
 
+sub parse_is_mountpoint {
+    my ($scfg) = @_;
+    my $is_mp = $scfg->{is_mountpoint};
+    return undef if !defined $is_mp;
+    if (defined(my $bool = PVE::JSONSchema::parse_boolean($is_mp))) {
+       return $bool ? $scfg->{path} : undef;
+    }
+    return $is_mp; # contains a path
+}
+
+# FIXME move into 'get_volume_attribute' when removing 'get_volume_notes'
+my $get_volume_notes_impl = sub {
+    my ($class, $scfg, $storeid, $volname, $timeout) = @_;
+
+    my ($vtype) = $class->parse_volname($volname);
+    return if $vtype ne 'backup';
+
+    my $path = $class->filesystem_path($scfg, $volname);
+    $path .= $class->SUPER::NOTES_EXT;
+
+    if (-f $path) {
+       my $data = PVE::Tools::file_get_contents($path);
+       return eval { decode('UTF-8', $data, 1) } // $data;
+    }
+
+    return '';
+};
+
+# FIXME remove on the next APIAGE reset.
+# Deprecated, use get_volume_attribute instead.
+sub get_volume_notes {
+    my ($class, $scfg, $storeid, $volname, $timeout) = @_;
+    return $get_volume_notes_impl->($class, $scfg, $storeid, $volname, $timeout);
+}
+
+# FIXME move into 'update_volume_attribute' when removing 'update_volume_notes'
+my $update_volume_notes_impl = sub {
+    my ($class, $scfg, $storeid, $volname, $notes, $timeout) = @_;
+
+    my ($vtype) = $class->parse_volname($volname);
+    die "only backups can have notes\n" if $vtype ne 'backup';
+
+    my $path = $class->filesystem_path($scfg, $volname);
+    $path .= $class->SUPER::NOTES_EXT;
+
+    if (defined($notes) && $notes ne '') {
+       my $encoded = encode('UTF-8', $notes);
+       PVE::Tools::file_set_contents($path, $encoded);
+    } else {
+       unlink $path or $! == ENOENT or die "could not delete notes - $!\n";
+    }
+    return;
+};
+
+# FIXME remove on the next APIAGE reset.
+# Deprecated, use update_volume_attribute instead.
+sub update_volume_notes {
+    my ($class, $scfg, $storeid, $volname, $notes, $timeout) = @_;
+    return $update_volume_notes_impl->($class, $scfg, $storeid, $volname, $notes, $timeout);
+}
+
+sub get_volume_attribute {
+    my ($class, $scfg, $storeid, $volname, $attribute) = @_;
+
+    if ($attribute eq 'notes') {
+       return $get_volume_notes_impl->($class, $scfg, $storeid, $volname);
+    }
+
+    my ($vtype) = $class->parse_volname($volname);
+    return if $vtype ne 'backup';
+
+    if ($attribute eq 'protected') {
+       my $path = $class->filesystem_path($scfg, $volname);
+       return -e PVE::Storage::protection_file_path($path) ? 1 : 0;
+    }
+
+    return;
+}
+
+sub update_volume_attribute {
+    my ($class, $scfg, $storeid, $volname, $attribute, $value) = @_;
+
+    if ($attribute eq 'notes') {
+       return $update_volume_notes_impl->($class, $scfg, $storeid, $volname, $value);
+    }
+
+    my ($vtype) = $class->parse_volname($volname);
+    die "only backups support attribute '$attribute'\n" if $vtype ne 'backup';
+
+    if ($attribute eq 'protected') {
+       my $path = $class->filesystem_path($scfg, $volname);
+       my $protection_path = PVE::Storage::protection_file_path($path);
+
+       return if !((-e $protection_path) xor $value); # protection status already correct
+
+       if ($value) {
+           my $fh = IO::File->new($protection_path, O_CREAT, 0644)
+               or die "unable to create protection file '$protection_path' - $!\n";
+           close($fh);
+       } else {
+           unlink $protection_path or $! == ENOENT
+               or die "could not delete protection file '$protection_path' - $!\n";
+       }
+
+       return;
+    }
+
+    die "attribute '$attribute' is not supported for storage type '$scfg->{type}'\n";
+}
+
 sub status {
     my ($class, $storeid, $scfg, $cache) = @_;
 
-    $cache->{mountdata} = PVE::ProcFSTools::parse_proc_mounts()
-       if !$cache->{mountdata};
+    if (defined(my $mp = parse_is_mountpoint($scfg))) {
+       $cache->{mountdata} = PVE::ProcFSTools::parse_proc_mounts()
+           if !$cache->{mountdata};
 
-    my $path = $scfg->{path};
-
-    return undef if !path_is_mounted($path, $cache->{mountdata});
+       return undef if !path_is_mounted($mp, $cache->{mountdata});
+    }
 
     return $class->SUPER::status($storeid, $scfg, $cache);
 }
@@ -95,12 +217,13 @@ sub activate_storage {
        mkpath $path;
     }
 
-    if ($scfg->{is_mountpoint} && !path_is_mounted($path, $cache->{mountdata})) {
+    my $mp = parse_is_mountpoint($scfg);
+    if (defined($mp) && !path_is_mounted($mp, $cache->{mountdata})) {
        die "unable to activate storage '$storeid' - " .
-           "directory is expected to be a mount point but is not mounted: '$path'\n";
+           "directory is expected to be a mount point but is not mounted: '$mp'\n";
     }
 
-    $class->SUPER::activate_storage($storeid, $scfg, $cache);    
+    $class->SUPER::activate_storage($storeid, $scfg, $cache);
 }
 
 sub check_config {