use POSIX qw(O_RDONLY O_WRONLY O_CREAT O_TRUNC);
use Fcntl ':flock';
use File::Path;
+use MIME::Base64 qw(encode_base64);
+
+use IO::Socket::IP;
+use IO::Socket::UNIX;
+use Socket qw(SOCK_STREAM);
use PVE::SafeSyslog;
use PVE::Cluster;
use base qw(PVE::CLIHandler);
-my $KNOWN_EXPORT_FORMATS = ['raw+size', 'tar+size', 'qcow2+size', 'vmdk+size', 'zfs'];
-
my $nodename = PVE::INotify::nodename();
sub param_mapping {
}
};
+ my $master_key_map = {
+ name => 'master-pubkey',
+ desc => 'a file containing a PEM-formatted master public key',
+ func => sub {
+ my ($value) = @_;
+ return encode_base64(PVE::Tools::file_get_contents($value), '');
+ }
+ };
+
+ my $keyring_map = {
+ name => 'keyring',
+ desc => 'file containing the keyring to authenticate in the Ceph cluster',
+ func => sub {
+ my ($value) = @_;
+ return PVE::Tools::file_get_contents($value);
+ },
+ };
my $mapping = {
'cifsscan' => [ $password_map ],
'cifs' => [ $password_map ],
- 'create' => [ $password_map, $enc_key_map ],
- 'update' => [ $password_map, $enc_key_map ],
+ 'pbs' => [ $password_map ],
+ 'create' => [ $password_map, $enc_key_map, $master_key_map, $keyring_map ],
+ 'update' => [ $password_map, $enc_key_map, $master_key_map, $keyring_map ],
};
return $mapping->{$name};
}
my $authuser = $rpcenv->get_user();
my $storage_cfg = PVE::Storage::config();
- PVE::Storage::check_volume_access($rpcenv, $authuser, $storage_cfg, undef, $volume);
+ PVE::Storage::check_volume_access(
+ $rpcenv,
+ $authuser,
+ $storage_cfg,
+ undef,
+ $volume,
+ 'backup',
+ );
+
+ if (PVE::Storage::parse_volume_id($volume, 1)) {
+ my (undef, undef, $ownervm) = PVE::Storage::parse_volname($storage_cfg, $volume);
+ $rpcenv->check($authuser, "/vms/$ownervm", ['VM.Backup']);
+ }
my $config_raw = PVE::Storage::extract_vzdump_config($storage_cfg, $volume);
format => {
description => "Export stream format",
type => 'string',
- enum => $KNOWN_EXPORT_FORMATS,
+ enum => $PVE::Storage::KNOWN_EXPORT_FORMATS,
},
filename => {
description => "Destination file name",
base => {
description => "Snapshot to start an incremental stream from",
type => 'string',
- pattern => qr/[a-z0-9_\-]{1,40}/,
+ pattern => qr/[a-z0-9_\-]{1,40}/i,
maxLength => 40,
optional => 1,
},
snapshot => {
description => "Snapshot to export",
type => 'string',
- pattern => qr/[a-z0-9_\-]{1,40}/,
+ pattern => qr/[a-z0-9_\-]{1,40}/i,
maxLength => 40,
optional => 1,
},
optional => 1,
default => 0,
},
+ 'snapshot-list' => {
+ description => "Ordered list of snapshots to transfer",
+ type => 'string',
+ format => 'string-list',
+ optional => 1,
+ },
},
},
returns => { type => 'null' },
code => sub {
my ($param) = @_;
+ my $with_snapshots = $param->{'with-snapshots'};
+ if (defined(my $list = $param->{'snapshot-list'})) {
+ $with_snapshots = PVE::Tools::split_list($list);
+ }
+
my $filename = $param->{filename};
my $outfh;
eval {
my $cfg = PVE::Storage::config();
PVE::Storage::volume_export($cfg, $outfh, $param->{volume}, $param->{format},
- $param->{snapshot}, $param->{base}, $param->{'with-snapshots'});
+ $param->{snapshot}, $param->{base}, $with_snapshots);
};
my $err = $@;
if ($filename ne '-') {
format => {
description => "Import stream format",
type => 'string',
- enum => $KNOWN_EXPORT_FORMATS,
+ enum => $PVE::Storage::KNOWN_EXPORT_FORMATS,
},
filename => {
description => "Source file name. For '-' stdin is used, the " .
- "tcp://<IP-or-CIDR> format allows to use a TCP connection as input. " .
+ "tcp://<IP-or-CIDR> format allows to use a TCP connection, " .
+ "the unix://PATH-TO-SOCKET format a UNIX socket as input." .
"Else, the file is treated as common file.",
type => 'string',
},
base => {
description => "Base snapshot of an incremental stream",
type => 'string',
- pattern => qr/[a-z0-9_\-]{1,40}/,
+ pattern => qr/[a-z0-9_\-]{1,40}/i,
maxLength => 40,
optional => 1,
},
'delete-snapshot' => {
description => "A snapshot to delete on success",
type => 'string',
- pattern => qr/[a-z0-9_\-]{1,80}/,
+ pattern => qr/[a-z0-9_\-]{1,80}/i,
maxLength => 80,
optional => 1,
},
optional => 1,
default => 0,
},
+ snapshot => {
+ description => "The current-state snapshot if the stream contains snapshots",
+ type => 'string',
+ pattern => qr/[a-z0-9_\-]{1,40}/i,
+ maxLength => 40,
+ optional => 1,
+ },
},
},
returns => { type => 'string' },
alarm $prev_alarm;
close($socket);
+ $infh = \*$client;
+ } elsif ($filename =~ m!^unix://(.*)$!) {
+ my $socket_path = $1;
+ my $socket = IO::Socket::UNIX->new(
+ Type => SOCK_STREAM(),
+ Local => $socket_path,
+ Listen => 1,
+ ) or die "failed to open socket: $!\n";
+
+ print "ready\n";
+ *STDOUT->flush();
+
+ my $prev_alarm = alarm 0;
+ local $SIG{ALRM} = sub { die "timed out waiting for client\n" };
+ alarm 30;
+ my $client = $socket->accept; # Wait for a client
+ alarm $prev_alarm;
+ close($socket);
+
$infh = \*$client;
} else {
sysopen($infh, $filename, O_RDONLY)
my $volume = $param->{volume};
my $delete = $param->{'delete-snapshot'};
my $imported_volid = PVE::Storage::volume_import($cfg, $infh, $volume, $param->{format},
- $param->{base}, $param->{'with-snapshots'}, $param->{'allow-rename'});
+ $param->{snapshot}, $param->{base}, $param->{'with-snapshots'},
+ $param->{'allow-rename'});
PVE::Storage::volume_snapshot_delete($cfg, $imported_volid, $delete)
if defined($delete);
return $imported_volid;
};
}});
+my $print_api_result = sub {
+ my ($data, $schema, $options) = @_;
+ PVE::CLIFormatter::print_api_result($data, $schema, undef, $options);
+};
+
our $cmddef = {
add => [ "PVE::API2::Storage::Config", 'create', ['type', 'storage'] ],
set => [ "PVE::API2::Storage::Config", 'update', ['storage'] ],
printf "$rec->{lv}\n";
}
}],
+ pbs => [
+ "PVE::API2::Storage::Scan",
+ 'pbsscan',
+ ['server', 'username'],
+ { node => $nodename },
+ $print_api_result,
+ $PVE::RESTHandler::standard_output_options,
+ ],
zfs => [ "PVE::API2::Storage::Scan", 'zfsscan', [], { node => $nodename }, sub {
my $res = shift;