Files
pve-storage/PVE/Storage/CIFSPlugin.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

313 lines
7.5 KiB
Perl

package PVE::Storage::CIFSPlugin;
use strict;
use warnings;
use Net::IP;
use PVE::Tools qw(run_command);
use PVE::ProcFSTools;
use File::Path;
use PVE::Storage::Plugin;
use PVE::JSONSchema qw(get_standard_option);
use base qw(PVE::Storage::Plugin);
# CIFS helper functions
sub cifs_is_mounted {
my ($server, $share, $mountpoint, $mountdata) = @_;
$server = "[$server]" if Net::IP::ip_is_ipv6($server);
my $source = "//${server}/$share";
$mountdata = PVE::ProcFSTools::parse_proc_mounts() if !$mountdata;
return $mountpoint if grep {
$_->[2] =~ /^cifs/ &&
$_->[0] =~ m|^\Q$source\E/?$| &&
$_->[1] eq $mountpoint
} @$mountdata;
return undef;
}
sub cifs_cred_file_name {
my ($storeid) = @_;
return "/etc/pve/priv/storage/${storeid}.pw";
}
sub cifs_delete_credentials {
my ($storeid) = @_;
if (my $cred_file = get_cred_file($storeid)) {
unlink($cred_file) or warn "removing cifs credientials '$cred_file' failed: $!\n";
}
}
sub cifs_set_credentials {
my ($password, $storeid) = @_;
my $cred_file = cifs_cred_file_name($storeid);
mkdir "/etc/pve/priv/storage";
PVE::Tools::file_set_contents($cred_file, "password=$password\n");
return $cred_file;
}
sub get_cred_file {
my ($storeid) = @_;
my $cred_file = cifs_cred_file_name($storeid);
if (-e $cred_file) {
return $cred_file;
}
return undef;
}
sub cifs_mount {
my ($server, $share, $mountpoint, $storeid, $smbver, $user, $domain) = @_;
$server = "[$server]" if Net::IP::ip_is_ipv6($server);
my $source = "//${server}/$share";
my $cmd = ['/bin/mount', '-t', 'cifs', $source, $mountpoint, '-o', 'soft', '-o'];
if (my $cred_file = get_cred_file($storeid)) {
push @$cmd, "username=$user", '-o', "credentials=$cred_file";
push @$cmd, '-o', "domain=$domain" if defined($domain);
} else {
push @$cmd, 'guest,username=guest';
}
push @$cmd, '-o', defined($smbver) ? "vers=$smbver" : "vers=default";
run_command($cmd, errmsg => "mount error");
}
# Configuration
sub type {
return 'cifs';
}
sub plugindata {
return {
content => [ { images => 1, rootdir => 1, vztmpl => 1, iso => 1,
backup => 1, snippets => 1}, { images => 1 }],
format => [ { raw => 1, qcow2 => 1, vmdk => 1 } , 'raw' ],
};
}
sub properties {
return {
share => {
description => "CIFS share.",
type => 'string',
},
password => {
description => "Password for accessing the share/datastore.",
type => 'string',
maxLength => 256,
},
domain => {
description => "CIFS domain.",
type => 'string',
optional => 1,
maxLength => 256,
},
smbversion => {
description => "SMB protocol version. 'default' if not set, negotiates the highest SMB2+"
." version supported by both the client and server.",
type => 'string',
default => 'default',
enum => ['default', '2.0', '2.1', '3', '3.0', '3.11'],
optional => 1,
},
};
}
sub options {
return {
path => { fixed => 1 },
server => { fixed => 1 },
share => { fixed => 1 },
nodes => { optional => 1 },
disable => { optional => 1 },
maxfiles => { optional => 1 },
'prune-backups' => { optional => 1 },
content => { optional => 1 },
format => { optional => 1 },
username => { optional => 1 },
password => { optional => 1},
domain => { optional => 1},
smbversion => { optional => 1},
mkdir => { optional => 1 },
bwlimit => { optional => 1 },
preallocation => { optional => 1 },
};
}
sub check_config {
my ($class, $sectionId, $config, $create, $skipSchemaCheck) = @_;
$config->{path} = "/mnt/pve/$sectionId" if $create && !$config->{path};
return $class->SUPER::check_config($sectionId, $config, $create, $skipSchemaCheck);
}
# Storage implementation
sub on_add_hook {
my ($class, $storeid, $scfg, %sensitive) = @_;
if (defined($sensitive{password})) {
cifs_set_credentials($sensitive{password}, $storeid);
if (!exists($scfg->{username})) {
warn "storage $storeid: ignoring password parameter, no user set\n";
}
} else {
cifs_delete_credentials($storeid);
}
return;
}
sub on_update_hook {
my ($class, $storeid, $scfg, %sensitive) = @_;
return if !exists($sensitive{password});
if (defined($sensitive{password})) {
cifs_set_credentials($sensitive{password}, $storeid);
if (!exists($scfg->{username})) {
warn "storage $storeid: ignoring password parameter, no user set\n";
}
} else {
cifs_delete_credentials($storeid);
}
return;
}
sub on_delete_hook {
my ($class, $storeid, $scfg) = @_;
cifs_delete_credentials($storeid);
return;
}
sub status {
my ($class, $storeid, $scfg, $cache) = @_;
$cache->{mountdata} = PVE::ProcFSTools::parse_proc_mounts()
if !$cache->{mountdata};
my $path = $scfg->{path};
my $server = $scfg->{server};
my $share = $scfg->{share};
return undef
if !cifs_is_mounted($server, $share, $path, $cache->{mountdata});
return $class->SUPER::status($storeid, $scfg, $cache);
}
sub activate_storage {
my ($class, $storeid, $scfg, $cache) = @_;
$cache->{mountdata} = PVE::ProcFSTools::parse_proc_mounts()
if !$cache->{mountdata};
my $path = $scfg->{path};
my $server = $scfg->{server};
my $share = $scfg->{share};
if (!cifs_is_mounted($server, $share, $path, $cache->{mountdata})) {
mkpath $path if !(defined($scfg->{mkdir}) && !$scfg->{mkdir});
die "unable to activate storage '$storeid' - " .
"directory '$path' does not exist\n" if ! -d $path;
cifs_mount($server, $share, $path, $storeid, $scfg->{smbversion},
$scfg->{username}, $scfg->{domain});
}
$class->SUPER::activate_storage($storeid, $scfg, $cache);
}
sub deactivate_storage {
my ($class, $storeid, $scfg, $cache) = @_;
$cache->{mountdata} = PVE::ProcFSTools::parse_proc_mounts()
if !$cache->{mountdata};
my $path = $scfg->{path};
my $server = $scfg->{server};
my $share = $scfg->{share};
if (cifs_is_mounted($server, $share, $path, $cache->{mountdata})) {
my $cmd = ['/bin/umount', $path];
run_command($cmd, errmsg => 'umount error');
}
}
sub check_connection {
my ($class, $storeid, $scfg) = @_;
my $servicename = '//'.$scfg->{server}.'/'.$scfg->{share};
my $cmd = ['/usr/bin/smbclient', $servicename, '-d', '0'];
if (defined($scfg->{smbversion}) && $scfg->{smbversion} ne 'default') {
# max-protocol version, so basically only relevant for smb2 vs smb3
push @$cmd, '-m', "smb" . int($scfg->{smbversion});
}
if (my $cred_file = get_cred_file($storeid)) {
push @$cmd, '-U', $scfg->{username}, '-A', $cred_file;
push @$cmd, '-W', $scfg->{domain} if defined($scfg->{domain});
} else {
push @$cmd, '-U', 'Guest','-N';
}
push @$cmd, '-c', 'echo 1 0';
my $out_str;
my $out = sub { $out_str .= shift };
eval { run_command($cmd, timeout => 10, outfunc => $out, errfunc => sub {}) };
if (my $err = $@) {
die "$out_str\n" if defined($out_str) &&
($out_str =~ m/NT_STATUS_ACCESS_DENIED/);
return 0;
}
return 1;
}
# FIXME remove on the next APIAGE reset.
# Deprecated, use get_volume_attribute instead.
sub get_volume_notes {
my $class = shift;
PVE::Storage::DirPlugin::get_volume_notes($class, @_);
}
# FIXME remove on the next APIAGE reset.
# Deprecated, use update_volume_attribute instead.
sub update_volume_notes {
my $class = shift;
PVE::Storage::DirPlugin::update_volume_notes($class, @_);
}
sub get_volume_attribute {
return PVE::Storage::DirPlugin::get_volume_attribute(@_);
}
sub update_volume_attribute {
return PVE::Storage::DirPlugin::update_volume_attribute(@_);
}
1;