Files
pve-storage/PVE/Storage/CIFSPlugin.pm
Leo Nunner 3e7bd7dac3 config: add overrides for default directory locations
Allowing overrides for the default directory locations seems to
integrate rather well into the existing system. Custom locations
are specified using the "dirs" parameter as a comma-separated list
of "vtype:/location" values.

For now, the option has been enabled for the Directory, CIFS and NFS
backends.

Signed-off-by: Leo Nunner <l.nunner@proxmox.com>
2023-01-05 14:33:16 +01:00

315 lines
7.6 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 },
dirs => { optional => 1 },
server => { fixed => 1 },
share => { fixed => 1 },
nodes => { optional => 1 },
disable => { optional => 1 },
maxfiles => { optional => 1 },
'prune-backups' => { optional => 1 },
'max-protected-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|LOGON_FAILURE)/);
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;