Files
pve-storage/PVE/Storage/DirPlugin.pm
Fabian Ebner f1de828166 add generalized functions to manage volume attributes
replacing the ones for handling notes. To ensure backwards
compatibility with external plugins, all plugins that do not just call
another implementation need to call $class->{get, update}_volume_notes
when the attribute is 'notes' to catch any derived implementations.

This is mainly done to avoid the need to add new methods every time a
new attribute is added.

Not adding a timeout parameter like the notes functions have, because
it was not used and can still be added if it ever is needed in the
future.

For get_volume_attribute, undef will indicate that the attribute is
not supported. This makes it possible to distinguish "not supported"
from "error getting the attribute", which is useful when the attribute
is important for an operation. For example, free_image checking for
protection (introduced in a later patch) can abort if getting the
'protected' attribute fails.

Suggested-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
2021-11-08 14:56:15 +01:00

191 lines
4.9 KiB
Perl

package PVE::Storage::DirPlugin;
use strict;
use warnings;
use Cwd;
use File::Path;
use POSIX;
use PVE::Storage::Plugin;
use PVE::JSONSchema qw(get_standard_option);
use base qw(PVE::Storage::Plugin);
# Configuration
sub type {
return 'dir';
}
sub plugindata {
return {
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 {
path => {
description => "File system path.",
type => 'string', format => 'pve-storage-path',
},
mkdir => {
description => "Create the directory if it doesn't exist.",
type => 'boolean',
default => 'yes',
},
is_mountpoint => {
description =>
"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 },
shared => { optional => 1 },
disable => { optional => 1 },
maxfiles => { optional => 1 },
'prune-backups' => { optional => 1 },
content => { optional => 1 },
format => { optional => 1 },
mkdir => { optional => 1 },
is_mountpoint => { optional => 1 },
bwlimit => { optional => 1 },
preallocation => { optional => 1 },
};
}
# Storage implementation
#
# NOTE: should ProcFSTools::is_mounted accept an optional cache like this?
sub path_is_mounted {
my ($mountpoint, $mountdata) = @_;
$mountpoint = Cwd::realpath($mountpoint); # symlinks
return 0 if !defined($mountpoint); # path does not exist
$mountdata = PVE::ProcFSTools::parse_proc_mounts() if !$mountdata;
return 1 if grep { $_->[1] eq $mountpoint } @$mountdata;
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 remove on the next APIAGE reset.
# Deprecated, use get_volume_attribute instead.
sub get_volume_notes {
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;
return PVE::Tools::file_get_contents($path) if -f $path;
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) = @_;
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 '') {
PVE::Tools::file_set_contents($path, $notes);
} else {
unlink $path or $! == ENOENT or die "could not delete notes - $!\n";
}
return;
}
sub get_volume_attribute {
my ($class, $scfg, $storeid, $volname, $attribute) = @_;
if ($attribute eq 'notes') {
return $class->get_volume_notes($scfg, $storeid, $volname);
}
return;
}
sub update_volume_attribute {
my ($class, $scfg, $storeid, $volname, $attribute, $value) = @_;
if ($attribute eq 'notes') {
return $class->update_volume_notes($scfg, $storeid, $volname, $value);
}
die "attribute '$attribute' is not supported for storage type '$scfg->{type}'\n";
}
sub status {
my ($class, $storeid, $scfg, $cache) = @_;
if (defined(my $mp = parse_is_mountpoint($scfg))) {
$cache->{mountdata} = PVE::ProcFSTools::parse_proc_mounts()
if !$cache->{mountdata};
return undef if !path_is_mounted($mp, $cache->{mountdata});
}
return $class->SUPER::status($storeid, $scfg, $cache);
}
sub activate_storage {
my ($class, $storeid, $scfg, $cache) = @_;
my $path = $scfg->{path};
if (!defined($scfg->{mkdir}) || $scfg->{mkdir}) {
mkpath $path;
}
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: '$mp'\n";
}
$class->SUPER::activate_storage($storeid, $scfg, $cache);
}
sub check_config {
my ($self, $sectionId, $config, $create, $skipSchemaCheck) = @_;
my $opts = PVE::SectionConfig::check_config($self, $sectionId, $config, $create, $skipSchemaCheck);
return $opts if !$create;
if ($opts->{path} !~ m@^/[-/a-zA-Z0-9_.]+$@) {
die "illegal path for directory storage: $opts->{path}\n";
}
return $opts;
}
1;