use Cwd 'abs_path';
use Getopt::Long qw(GetOptionsFromArray);
use Socket;
-use Digest::SHA1;
+use Digest::SHA;
use Net::Ping;
-use PVE::Tools qw(run_command file_read_firstline trim);
+use PVE::Tools qw(run_command file_read_firstline trim dir_glob_regex dir_glob_foreach);
use PVE::Cluster qw(cfs_register_file cfs_read_file cfs_write_file cfs_lock_file);
use PVE::Exception qw(raise_param_exc);
use PVE::JSONSchema;
use PVE::INotify;
+use PVE::RPCEnvironment;
my $ISCSIADM = '/usr/bin/iscsiadm';
my $UDEVADM = '/sbin/udevadm';
return $stable_paths;
}
-sub dir_glob_regex {
- my ($dir, $regex) = @_;
-
- my $dh = IO::Dir->new ($dir);
- return wantarray ? () : undef if !$dh;
-
- while (defined(my $tmp = $dh->read)) {
- if (my @res = $tmp =~ m/^($regex)$/) {
- $dh->close;
- return wantarray ? @res : $tmp;
- }
- }
- $dh->close;
-
- return wantarray ? () : undef;
-}
-
-sub dir_glob_foreach {
- my ($dir, $regex, $func) = @_;
-
- my $dh = IO::Dir->new ($dir);
- if (defined $dh) {
- while (defined(my $tmp = $dh->read)) {
- if (my @res = $tmp =~ m/^($regex)$/) {
- &$func (@res);
- }
- }
- }
-}
-
sub read_proc_mounts {
local $/; # enable slurp mode
path => 'path',
shared => 'bool',
disable => 'bool',
+ saferemove => 'bool',
format => 'format',
content => 'content',
server => 'server',
target => 'target',
nodes => 'nodes',
options => 'options',
+ maxfiles => 'natural',
};
my $required_config = {
nodes => 0,
shared => 0,
disable => 0,
+ maxfiles => 0,
content => [ { images => 1, rootdir => 1, vztmpl => 1, iso => 1, backup => 1, none => 1 },
{ images => 1, rootdir => 1 }],
format => [ { raw => 1, qcow2 => 1, vmdk => 1 } , 'raw' ],
server => 1,
export => 1,
options => 0,
+ maxfiles => 0,
content => [ { images => 1, rootdir => 1, vztmpl => 1, iso => 1, backup => 1},
{ images => 1 }],
format => [ { raw => 1, qcow2 => 1, vmdk => 1 } , 'raw' ],
nodes => 0,
shared => 0,
disable => 0,
+ saferemove => 0,
content => [ {images => 1}, { images => 1 }],
base => 1,
},
return parse_lvm_name ($value, $noerr);
} elsif ($ct eq 'portal') {
return verify_portal($value, $noerr);
+ } elsif ($ct eq 'natural') {
+ return int($value) if $value =~ m/^\d+$/;
+ return undef if $noerr;
+ die "type check ('natural') failed - got '$value'\n";
} elsif ($ct eq 'nodes') {
my $res = {};
my $ids = {};
- my $digest = Digest::SHA1::sha1_hex(defined($raw) ? $raw : '');
+ my $digest = Digest::SHA::sha1_hex(defined($raw) ? $raw : '');
my $pri = 0;
my $type = $1;
my $ignore = 0;
- if (!parse_storage_id ($storeid, 1)) {
+ if (!PVE::JSONSchema::parse_storage_id($storeid, 1)) {
$ignore = 1;
warn "ignoring storage '$storeid' - (illegal characters)\n";
} elsif (!$default_config->{$type}) {
$ids->{local} = {
type => 'dir',
priority => $pri++,
- path => '/var/lib/vz',
+ path => '/var/lib/vz',
+ maxfiles => 0,
content => { images => 1, rootdir => 1, vztmpl => 1, iso => 1},
};
}
if ($scfg->{nodes}) {
$node = PVE::INotify::nodename() if !$node || ($node eq 'localhost');
if (!$scfg->{nodes}->{$node}) {
- die "storage '$storeid' is not available on node '$node'" if !$noerr;
+ die "storage '$storeid' is not available on node '$node'\n" if !$noerr;
return undef;
}
}
foreach my $k (keys %$def) {
next if defined ($done_hash->{$k});
- if (defined (my $v = $ids->{$storeid}->{$k})) {
- $data .= sprint_config_line ($k, $v);
- }
+ my $v = $ids->{$storeid}->{$k};
+ next if !defined($v);
+ $data .= sprint_config_line ($k, $v);
}
$out .= "$data\n";
$res->{$target}->{$volid} = {
'format' => 'raw',
- 'size' => int($size / 2),
+ 'size' => int($size * 512),
'vmid' => 0, # not assigned to any vm
'channel' => int($channel),
'id' => int($id),
# library implementation
-
-PVE::JSONSchema::register_format('pve-storage-id', \&parse_storage_id);
-sub parse_storage_id {
- my ($storeid, $noerr) = @_;
-
- if ($storeid !~ m/^[a-z][a-z0-9\-\_\.]*[a-z0-9]$/i) {
- return undef if $noerr;
- die "storage ID '$storeid' contains illegal characters\n";
- }
- return $storeid;
-}
-
-PVE::JSONSchema::register_standard_option('pve-storage-id', {
- description => "The storage identifier.",
- type => 'string', format => 'pve-storage-id',
-});
-
PVE::JSONSchema::register_format('pve-storage-vgname', \&parse_lvm_name);
sub parse_lvm_name {
my ($name, $noerr) = @_;
return ('vztmpl', $1);
} elsif ($volname =~ m!^rootdir/(\d+)$!) {
return ('rootdir', $1, $1);
- } elsif ($volname =~ m!^backup/([^/]+(\.tar|\.tgz))$!) {
- return ('backup', $1);
+ } elsif ($volname =~ m!^backup/([^/]+(\.(tar|tar\.gz|tar\.lzo|tgz)))$!) {
+ my $fn = $1;
+ if ($fn =~ m/^vzdump-(openvz|qemu)-(\d+)-.+/) {
+ return ('backup', $fn, $2);
+ }
+ return ('backup', $fn);
}
die "unable to parse directory volume name '$volname'\n";
}
return ('');
}
- $path = abs_path ($path);
+ # Note: abs_path() return undef if $path doesn not exist
+ # for example when nfs storage is not mounted
+ $path = abs_path($path) || $path;
foreach my $sid (keys %$ids) {
my $type = $ids->{$sid}->{type};
} elsif ($path =~ m!^$privatedir/(\d+)$!) {
my $vmid = $1;
return ('rootdir', "$sid:rootdir/$vmid");
- } elsif ($path =~ m!^$backupdir/([^/]+\.(tar|tgz))$!) {
+ } elsif ($path =~ m!^$backupdir/([^/]+\.(tar|tar\.gz|tar\.lzo|tgz))$!) {
my $name = $1;
return ('iso', "$sid:backup/$name");
}
my $path;
my $owner;
+ my $vtype = 'image';
if ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs') {
- my ($vtype, $name, $vmid) = parse_volname_dir ($volname);
+ my ($name, $vmid);
+ ($vtype, $name, $vmid) = parse_volname_dir ($volname);
$owner = $vmid;
my $imagedir = get_image_dir($cfg, $storeid, $vmid);
die "unknown storage type '$scfg->{type}'";
}
- return wantarray ? ($path, $owner) : $path;
+ return wantarray ? ($path, $owner, $vtype) : $path;
}
sub storage_migrate {
die "no storage id specified\n" if !$storeid;
- parse_storage_id ($storeid);
+ PVE::JSONSchema::parse_storage_id($storeid);
- my $scfg = storage_config ($cfg, $storeid);
+ my $scfg = storage_config($cfg, $storeid);
die "no VMID specified\n" if !$vmid;
my $path = "$imagedir/$name";
- die "disk image '$path' already exists\n" if -f $path;
+ die "disk image '$path' already exists\n" if -e $path;
run_command("/usr/bin/qemu-img create -f $fmt '$path' ${size}K",
errmsg => "unable to create image");
activate_storage ($cfg, $storeid);
+ # we need to zero out LVM data for security reasons
+ # and to allow thin provisioning
+
+ my $vg;
+
# lock shared storage
cluster_lock_storage($storeid, $scfg->{shared}, undef, sub {
}
} elsif ($scfg->{type} eq 'lvm') {
- my $vg = $scfg->{vgname};
-
- my $cmd = ['/sbin/lvremove', '-f', "$vg/$volname"];
+ if ($scfg->{saferemove}) {
+ # avoid long running task, so we only rename here
+ $vg = $scfg->{vgname};
+ my $cmd = ['/sbin/lvrename', $vg, $volname, "del-$volname"];
+ run_command($cmd, errmsg => "lvrename '$vg/$volname' error");
+ } else {
+ my $tmpvg = $scfg->{vgname};
+ my $cmd = ['/sbin/lvremove', '-f', "$tmpvg/$volname"];
+ run_command($cmd, errmsg => "lvremove '$tmpvg/$volname' error");
+ }
- run_command($cmd, errmsg => "lvremove '$vg/$volname' error");
} elsif ($scfg->{type} eq 'iscsi') {
die "can't free space in iscsi storage\n";
} else {
die "unknown storage type '$scfg->{type}'";
}
});
+
+ return if !$vg;
+
+ my $zero_out_worker = sub {
+ print "zero-out data on image $volname\n";
+ my $cmd = ['dd', "if=/dev/zero", "of=/dev/$vg/del-$volname", "bs=1M"];
+ eval { run_command($cmd, errmsg => "zero out failed"); };
+ warn $@ if $@;
+
+ cluster_lock_storage($storeid, $scfg->{shared}, undef, sub {
+ my $cmd = ['/sbin/lvremove', '-f', "$vg/del-$volname"];
+ run_command($cmd, errmsg => "lvremove '$vg/del-$volname' error");
+ });
+ print "successfully removed volume $volname\n";
+ };
+
+ my $rpcenv = PVE::RPCEnvironment::get();
+ my $authuser = $rpcenv->get_user();
+
+ $rpcenv->fork_worker('imgdel', undef, $authuser, $zero_out_worker);
}
# lvm utility functions
$info = { volid => "$sid:vztmpl/$1", format => 'tgz' };
} elsif ($tt eq 'backup') {
- next if $fn !~ m!/([^/]+\.(tar|tgz))$!;
+ next if $fn !~ m!/([^/]+\.(tar|tar\.gz|tar\.lzo|tgz))$!;
$info = { volid => "$sid:backup/$1", format => $2 };
}
$mountdata = read_proc_mounts() if !$mountdata;
- if ($mountdata =~ m/^$source\s$mountpoint\snfs/m) {
+ if ($mountdata =~ m|^$source/?\s$mountpoint\snfs|m) {
return $mountpoint;
}
# check is volume exists
if ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs') {
- die "volume '$volid' does not exist\n" if ! -f $path;
+ die "volume '$volid' does not exist\n" if ! -e $path;
} else {
die "volume '$volid' does not exist\n" if ! -b $path;
}
return if !($vollist && scalar(@$vollist));
- my $lvs = lvm_lvs ();
-
my @errlist = ();
foreach my $volid (@$vollist) {
my ($storeid, $volname) = parse_volume_id ($volid);
my $scfg = storage_config ($cfg, $storeid);
if ($scfg->{type} eq 'lvm') {
- my ($name) = parse_volname_lvm ($volname);
-
- if ($lvs->{$scfg->{vgname}}->{$name}) {
- my $path = path ($cfg, $volid);
- my $cmd = ['/sbin/lvchange', '-aln', $path];
- eval { run_command($cmd, errmsg => "can't deactivate LV '$volid'"); };
- if (my $err = $@) {
- warn $err;
- push @errlist, $volid;
- }
+ my $path = path ($cfg, $volid);
+ next if ! -b $path;
+
+ my $cmd = ['/sbin/lvchange', '-aln', $path];
+ eval { run_command($cmd, errmsg => "can't deactivate LV '$volid'"); };
+ if (my $err = $@) {
+ warn $err;
+ push @errlist, $volid;
}
}
}