]> git.proxmox.com Git - pve-storage.git/blob - PVE/Storage/Plugin.pm
base plugin: return ctime for vm images
[pve-storage.git] / PVE / Storage / Plugin.pm
1 package PVE::Storage::Plugin;
2
3 use strict;
4 use warnings;
5
6 use Fcntl ':mode';
7 use File::chdir;
8 use File::Path;
9 use File::Basename;
10 use Time::Local qw(timelocal);
11
12 use PVE::Tools qw(run_command);
13 use PVE::JSONSchema qw(get_standard_option);
14 use PVE::Cluster qw(cfs_register_file);
15
16 use JSON;
17
18 use base qw(PVE::SectionConfig);
19
20 our @COMMON_TAR_FLAGS = qw(
21 --one-file-system
22 -p --sparse --numeric-owner --acls
23 --xattrs --xattrs-include=user.* --xattrs-include=security.capability
24 --warning=no-file-ignored --warning=no-xattr-write
25 );
26
27 our @SHARED_STORAGE = (
28 'iscsi',
29 'nfs',
30 'cifs',
31 'rbd',
32 'cephfs',
33 'iscsidirect',
34 'glusterfs',
35 'zfs',
36 'drbd');
37
38 our $MAX_VOLUMES_PER_GUEST = 1024;
39
40 cfs_register_file ('storage.cfg',
41 sub { __PACKAGE__->parse_config(@_); },
42 sub { __PACKAGE__->write_config(@_); });
43
44
45 my $defaultData = {
46 propertyList => {
47 type => { description => "Storage type." },
48 storage => get_standard_option('pve-storage-id',
49 { completion => \&PVE::Storage::complete_storage }),
50 nodes => get_standard_option('pve-node-list', { optional => 1 }),
51 content => {
52 description => "Allowed content types.\n\nNOTE: the value " .
53 "'rootdir' is used for Containers, and value 'images' for VMs.\n",
54 type => 'string', format => 'pve-storage-content-list',
55 optional => 1,
56 completion => \&PVE::Storage::complete_content_type,
57 },
58 disable => {
59 description => "Flag to disable the storage.",
60 type => 'boolean',
61 optional => 1,
62 },
63 maxfiles => {
64 description => "Maximal number of backup files per VM. Use '0' for unlimted.",
65 type => 'integer',
66 minimum => 0,
67 optional => 1,
68 },
69 shared => {
70 description => "Mark storage as shared.",
71 type => 'boolean',
72 optional => 1,
73 },
74 'format' => {
75 description => "Default image format.",
76 type => 'string', format => 'pve-storage-format',
77 optional => 1,
78 },
79 },
80 };
81
82 sub content_hash_to_string {
83 my $hash = shift;
84
85 my @cta;
86 foreach my $ct (keys %$hash) {
87 push @cta, $ct if $hash->{$ct};
88 }
89
90 return join(',', @cta);
91 }
92
93 sub valid_content_types {
94 my ($type) = @_;
95
96 my $def = $defaultData->{plugindata}->{$type};
97
98 return {} if !$def;
99
100 return $def->{content}->[0];
101 }
102
103 sub default_format {
104 my ($scfg) = @_;
105
106 my $type = $scfg->{type};
107 my $def = $defaultData->{plugindata}->{$type};
108
109 my $def_format = 'raw';
110 my $valid_formats = [ $def_format ];
111
112 if (defined($def->{format})) {
113 $def_format = $scfg->{format} || $def->{format}->[1];
114 $valid_formats = [ sort keys %{$def->{format}->[0]} ];
115 }
116
117 return wantarray ? ($def_format, $valid_formats) : $def_format;
118 }
119
120 PVE::JSONSchema::register_format('pve-storage-path', \&verify_path);
121 sub verify_path {
122 my ($path, $noerr) = @_;
123
124 # fixme: exclude more shell meta characters?
125 # we need absolute paths
126 if ($path !~ m|^/[^;\(\)]+|) {
127 return undef if $noerr;
128 die "value does not look like a valid absolute path\n";
129 }
130 return $path;
131 }
132
133 PVE::JSONSchema::register_format('pve-storage-server', \&verify_server);
134 sub verify_server {
135 my ($server, $noerr) = @_;
136
137 if (!(PVE::JSONSchema::pve_verify_ip($server, 1) ||
138 PVE::JSONSchema::pve_verify_dns_name($server, 1)))
139 {
140 return undef if $noerr;
141 die "value does not look like a valid server name or IP address\n";
142 }
143 return $server;
144 }
145
146 PVE::JSONSchema::register_format('pve-storage-vgname', \&parse_lvm_name);
147 sub parse_lvm_name {
148 my ($name, $noerr) = @_;
149
150 if ($name !~ m/^[a-z0-9][a-z0-9\-\_\.]*[a-z0-9]$/i) {
151 return undef if $noerr;
152 die "lvm name '$name' contains illegal characters\n";
153 }
154
155 return $name;
156 }
157
158 # fixme: do we need this
159 #PVE::JSONSchema::register_format('pve-storage-portal', \&verify_portal);
160 #sub verify_portal {
161 # my ($portal, $noerr) = @_;
162 #
163 # # IP with optional port
164 # if ($portal !~ m/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(:\d+)?$/) {
165 # return undef if $noerr;
166 # die "value does not look like a valid portal address\n";
167 # }
168 # return $portal;
169 #}
170
171 PVE::JSONSchema::register_format('pve-storage-portal-dns', \&verify_portal_dns);
172 sub verify_portal_dns {
173 my ($portal, $noerr) = @_;
174
175 # IP or DNS name with optional port
176 if (!PVE::Tools::parse_host_and_port($portal)) {
177 return undef if $noerr;
178 die "value does not look like a valid portal address\n";
179 }
180 return $portal;
181 }
182
183 PVE::JSONSchema::register_format('pve-storage-content', \&verify_content);
184 sub verify_content {
185 my ($ct, $noerr) = @_;
186
187 my $valid_content = valid_content_types('dir'); # dir includes all types
188
189 if (!$valid_content->{$ct}) {
190 return undef if $noerr;
191 die "invalid content type '$ct'\n";
192 }
193
194 return $ct;
195 }
196
197 PVE::JSONSchema::register_format('pve-storage-format', \&verify_format);
198 sub verify_format {
199 my ($fmt, $noerr) = @_;
200
201 if ($fmt !~ m/(raw|qcow2|vmdk|subvol)/) {
202 return undef if $noerr;
203 die "invalid format '$fmt'\n";
204 }
205
206 return $fmt;
207 }
208
209 PVE::JSONSchema::register_format('pve-storage-options', \&verify_options);
210 sub verify_options {
211 my ($value, $noerr) = @_;
212
213 # mount options (see man fstab)
214 if ($value !~ m/^\S+$/) {
215 return undef if $noerr;
216 die "invalid options '$value'\n";
217 }
218
219 return $value;
220 }
221
222 PVE::JSONSchema::register_format('pve-volume-id', \&parse_volume_id);
223 sub parse_volume_id {
224 my ($volid, $noerr) = @_;
225
226 if ($volid =~ m/^([a-z][a-z0-9\-\_\.]*[a-z0-9]):(.+)$/i) {
227 return wantarray ? ($1, $2) : $1;
228 }
229 return undef if $noerr;
230 die "unable to parse volume ID '$volid'\n";
231 }
232
233
234 sub private {
235 return $defaultData;
236 }
237
238 sub parse_section_header {
239 my ($class, $line) = @_;
240
241 if ($line =~ m/^(\S+):\s*(\S+)\s*$/) {
242 my ($type, $storeid) = (lc($1), $2);
243 my $errmsg = undef; # set if you want to skip whole section
244 eval { PVE::JSONSchema::parse_storage_id($storeid); };
245 $errmsg = $@ if $@;
246 my $config = {}; # to return additional attributes
247 return ($type, $storeid, $errmsg, $config);
248 }
249 return undef;
250 }
251
252 sub decode_value {
253 my ($class, $type, $key, $value) = @_;
254
255 my $def = $defaultData->{plugindata}->{$type};
256
257 if ($key eq 'content') {
258 my $valid_content = $def->{content}->[0];
259
260 my $res = {};
261
262 foreach my $c (PVE::Tools::split_list($value)) {
263 if (!$valid_content->{$c}) {
264 warn "storage does not support content type '$c'\n";
265 next;
266 }
267 $res->{$c} = 1;
268 }
269
270 if ($res->{none} && scalar (keys %$res) > 1) {
271 die "unable to combine 'none' with other content types\n";
272 }
273
274 return $res;
275 } elsif ($key eq 'format') {
276 my $valid_formats = $def->{format}->[0];
277
278 if (!$valid_formats->{$value}) {
279 warn "storage does not support format '$value'\n";
280 next;
281 }
282
283 return $value;
284 } elsif ($key eq 'nodes') {
285 my $res = {};
286
287 foreach my $node (PVE::Tools::split_list($value)) {
288 if (PVE::JSONSchema::pve_verify_node_name($node)) {
289 $res->{$node} = 1;
290 }
291 }
292
293 # fixme:
294 # no node restrictions for local storage
295 #if ($storeid && $storeid eq 'local' && scalar(keys(%$res))) {
296 # die "storage '$storeid' does not allow node restrictions\n";
297 #}
298
299 return $res;
300 }
301
302 return $value;
303 }
304
305 sub encode_value {
306 my ($class, $type, $key, $value) = @_;
307
308 if ($key eq 'nodes') {
309 return join(',', keys(%$value));
310 } elsif ($key eq 'content') {
311 my $res = content_hash_to_string($value) || 'none';
312 return $res;
313 }
314
315 return $value;
316 }
317
318 sub parse_config {
319 my ($class, $filename, $raw) = @_;
320
321 my $cfg = $class->SUPER::parse_config($filename, $raw);
322 my $ids = $cfg->{ids};
323
324 # make sure we have a reasonable 'local:' storage
325 # we want 'local' to be always the same 'type' (on all cluster nodes)
326 if (!$ids->{local} || $ids->{local}->{type} ne 'dir' ||
327 ($ids->{local}->{path} && $ids->{local}->{path} ne '/var/lib/vz')) {
328 $ids->{local} = {
329 type => 'dir',
330 priority => 0, # force first entry
331 path => '/var/lib/vz',
332 maxfiles => 0,
333 content => { images => 1, rootdir => 1, vztmpl => 1, iso => 1, snippets => 1},
334 };
335 }
336
337 # make sure we have a path
338 $ids->{local}->{path} = '/var/lib/vz' if !$ids->{local}->{path};
339
340 # remove node restrictions for local storage
341 delete($ids->{local}->{nodes});
342
343 foreach my $storeid (keys %$ids) {
344 my $d = $ids->{$storeid};
345 my $type = $d->{type};
346
347 my $def = $defaultData->{plugindata}->{$type};
348
349 if ($def->{content}) {
350 $d->{content} = $def->{content}->[1] if !$d->{content};
351 }
352 if (grep { $_ eq $type } @SHARED_STORAGE) {
353 $d->{shared} = 1;
354 }
355 }
356
357 return $cfg;
358 }
359
360 # Storage implementation
361
362 # called during addition of storage (before the new storage config got written)
363 # die to abort additon if there are (grave) problems
364 # NOTE: runs in a storage config *locked* context
365 sub on_add_hook {
366 my ($class, $storeid, $scfg, %param) = @_;
367
368 # do nothing by default
369 }
370
371 # called during storage configuration update (before the updated storage config got written)
372 # die to abort the update if there are (grave) problems
373 # NOTE: runs in a storage config *locked* context
374 sub on_update_hook {
375 my ($class, $storeid, $scfg, %param) = @_;
376
377 # do nothing by default
378 }
379
380 # called during deletion of storage (before the new storage config got written)
381 # and if the activate check on addition fails, to cleanup all storage traces
382 # which on_add_hook may have created.
383 # die to abort deletion if there are (very grave) problems
384 # NOTE: runs in a storage config *locked* context
385 sub on_delete_hook {
386 my ($class, $storeid, $scfg) = @_;
387
388 # do nothing by default
389 }
390
391 sub cluster_lock_storage {
392 my ($class, $storeid, $shared, $timeout, $func, @param) = @_;
393
394 my $res;
395 if (!$shared) {
396 my $lockid = "pve-storage-$storeid";
397 my $lockdir = "/var/lock/pve-manager";
398 mkdir $lockdir;
399 $res = PVE::Tools::lock_file("$lockdir/$lockid", $timeout, $func, @param);
400 die $@ if $@;
401 } else {
402 $res = PVE::Cluster::cfs_lock_storage($storeid, $timeout, $func, @param);
403 die $@ if $@;
404 }
405 return $res;
406 }
407
408 sub parse_name_dir {
409 my $name = shift;
410
411 if ($name =~ m!^((base-)?[^/\s]+\.(raw|qcow2|vmdk|subvol))$!) {
412 return ($1, $3, $2); # (name, format, isBase)
413 }
414
415 die "unable to parse volume filename '$name'\n";
416 }
417
418 sub parse_volname {
419 my ($class, $volname) = @_;
420
421 if ($volname =~ m!^(\d+)/(\S+)/(\d+)/(\S+)$!) {
422 my ($basedvmid, $basename) = ($1, $2);
423 parse_name_dir($basename);
424 my ($vmid, $name) = ($3, $4);
425 my (undef, $format, $isBase) = parse_name_dir($name);
426 return ('images', $name, $vmid, $basename, $basedvmid, $isBase, $format);
427 } elsif ($volname =~ m!^(\d+)/(\S+)$!) {
428 my ($vmid, $name) = ($1, $2);
429 my (undef, $format, $isBase) = parse_name_dir($name);
430 return ('images', $name, $vmid, undef, undef, $isBase, $format);
431 } elsif ($volname =~ m!^iso/([^/]+$PVE::Storage::iso_extension_re)$!) {
432 return ('iso', $1);
433 } elsif ($volname =~ m!^vztmpl/([^/]+\.tar\.[gx]z)$!) {
434 return ('vztmpl', $1);
435 } elsif ($volname =~ m!^rootdir/(\d+)$!) {
436 return ('rootdir', $1, $1);
437 } elsif ($volname =~ m!^backup/([^/]+(\.(tar|tar\.gz|tar\.lzo|tgz|vma|vma\.gz|vma\.lzo)))$!) {
438 my $fn = $1;
439 if ($fn =~ m/^vzdump-(openvz|lxc|qemu)-(\d+)-.+/) {
440 return ('backup', $fn, $2);
441 }
442 return ('backup', $fn);
443 } elsif ($volname =~ m!^snippets/([^/]+)$!) {
444 return ('snippets', $1);
445 }
446
447 die "unable to parse directory volume name '$volname'\n";
448 }
449
450 my $vtype_subdirs = {
451 images => 'images',
452 rootdir => 'private',
453 iso => 'template/iso',
454 vztmpl => 'template/cache',
455 backup => 'dump',
456 snippets => 'snippets',
457 };
458
459 sub get_subdir {
460 my ($class, $scfg, $vtype) = @_;
461
462 my $path = $scfg->{path};
463
464 die "storage definintion has no path\n" if !$path;
465
466 my $subdir = $vtype_subdirs->{$vtype};
467
468 die "unknown vtype '$vtype'\n" if !defined($subdir);
469
470 return "$path/$subdir";
471 }
472
473 sub filesystem_path {
474 my ($class, $scfg, $volname, $snapname) = @_;
475
476 my ($vtype, $name, $vmid, undef, undef, $isBase, $format) =
477 $class->parse_volname($volname);
478
479 # Note: qcow2/qed has internal snapshot, so path is always
480 # the same (with or without snapshot => same file).
481 die "can't snapshot this image format\n"
482 if defined($snapname) && $format !~ m/^(qcow2|qed)$/;
483
484 my $dir = $class->get_subdir($scfg, $vtype);
485
486 $dir .= "/$vmid" if $vtype eq 'images';
487
488 my $path = "$dir/$name";
489
490 return wantarray ? ($path, $vmid, $vtype) : $path;
491 }
492
493 sub path {
494 my ($class, $scfg, $volname, $storeid, $snapname) = @_;
495
496 return $class->filesystem_path($scfg, $volname, $snapname);
497 }
498
499 sub create_base {
500 my ($class, $storeid, $scfg, $volname) = @_;
501
502 # this only works for file based storage types
503 die "storage definition has no path\n" if !$scfg->{path};
504
505 my ($vtype, $name, $vmid, $basename, $basevmid, $isBase, $format) =
506 $class->parse_volname($volname);
507
508 die "create_base on wrong vtype '$vtype'\n" if $vtype ne 'images';
509
510 die "create_base not possible with base image\n" if $isBase;
511
512 my $path = $class->filesystem_path($scfg, $volname);
513
514 my ($size, undef, $used, $parent) = file_size_info($path);
515 die "file_size_info on '$volname' failed\n" if !($format && defined($size));
516
517 die "volname '$volname' contains wrong information about parent\n"
518 if $basename && (!$parent || $parent ne "../$basevmid/$basename");
519
520 my $newname = $name;
521 $newname =~ s/^vm-/base-/;
522
523 my $newvolname = $basename ? "$basevmid/$basename/$vmid/$newname" :
524 "$vmid/$newname";
525
526 my $newpath = $class->filesystem_path($scfg, $newvolname);
527
528 die "file '$newpath' already exists\n" if -f $newpath;
529
530 rename($path, $newpath) ||
531 die "rename '$path' to '$newpath' failed - $!\n";
532
533 # We try to protect base volume
534
535 chmod(0444, $newpath); # nobody should write anything
536
537 # also try to set immutable flag
538 eval { run_command(['/usr/bin/chattr', '+i', $newpath]); };
539 warn $@ if $@;
540
541 return $newvolname;
542 }
543
544 my $get_vm_disk_number = sub {
545 my ($disk_name, $scfg, $vmid, $suffix) = @_;
546
547 my $disk_regex = qr/(vm|base)-$vmid-disk-(\d+)$suffix/;
548
549 my $type = $scfg->{type};
550 my $def = { %{$defaultData->{plugindata}->{$type}} };
551
552 my $valid = $def->{format}[0];
553 if ($valid->{subvol}) {
554 $disk_regex = qr/(vm|base|subvol|basevol)-$vmid-disk-(\d+)/;
555 }
556
557 if ($disk_name =~ m/$disk_regex/) {
558 return $2;
559 }
560
561 return undef;
562 };
563
564 sub get_next_vm_diskname {
565 my ($disk_list, $storeid, $vmid, $fmt, $scfg, $add_fmt_suffix) = @_;
566
567 $fmt //= '';
568 my $prefix = ($fmt eq 'subvol') ? 'subvol' : 'vm';
569 my $suffix = $add_fmt_suffix ? ".$fmt" : '';
570
571 my $disk_ids = {};
572 foreach my $disk (@$disk_list) {
573 my $disknum = $get_vm_disk_number->($disk, $scfg, $vmid, $suffix);
574 $disk_ids->{$disknum} = 1 if defined($disknum);
575 }
576
577 for (my $i = 0; $i < $MAX_VOLUMES_PER_GUEST; $i++) {
578 if (!$disk_ids->{$i}) {
579 return "$prefix-$vmid-disk-$i$suffix";
580 }
581 }
582
583 die "unable to allocate an image name for VM $vmid in storage '$storeid'\n"
584 }
585
586 sub find_free_diskname {
587 my ($class, $storeid, $scfg, $vmid, $fmt, $add_fmt_suffix) = @_;
588
589 my $disks = $class->list_images($storeid, $scfg, $vmid);
590
591 my $disk_list = [ map { $_->{volid} } @$disks ];
592
593 return get_next_vm_diskname($disk_list, $storeid, $vmid, $fmt, $scfg, $add_fmt_suffix);
594 }
595
596 sub clone_image {
597 my ($class, $scfg, $storeid, $volname, $vmid, $snap) = @_;
598
599 # this only works for file based storage types
600 die "storage definintion has no path\n" if !$scfg->{path};
601
602 my ($vtype, $basename, $basevmid, undef, undef, $isBase, $format) =
603 $class->parse_volname($volname);
604
605 die "clone_image on wrong vtype '$vtype'\n" if $vtype ne 'images';
606
607 die "this storage type does not support clone_image on snapshot\n" if $snap;
608
609 die "this storage type does not support clone_image on subvolumes\n" if $format eq 'subvol';
610
611 die "clone_image only works on base images\n" if !$isBase;
612
613 my $imagedir = $class->get_subdir($scfg, 'images');
614 $imagedir .= "/$vmid";
615
616 mkpath $imagedir;
617
618 my $name = $class->find_free_diskname($imagedir, $scfg, $vmid, "qcow2", 1);
619
620 warn "clone $volname: $vtype, $name, $vmid to $name (base=../$basevmid/$basename)\n";
621
622 my $newvol = "$basevmid/$basename/$vmid/$name";
623
624 my $path = $class->filesystem_path($scfg, $newvol);
625
626 # Note: we use relative paths, so we need to call chdir before qemu-img
627 eval {
628 local $CWD = $imagedir;
629
630 my $cmd = ['/usr/bin/qemu-img', 'create', '-b', "../$basevmid/$basename",
631 '-f', 'qcow2', $path];
632
633 run_command($cmd);
634 };
635 my $err = $@;
636
637 die $err if $err;
638
639 return $newvol;
640 }
641
642 sub alloc_image {
643 my ($class, $storeid, $scfg, $vmid, $fmt, $name, $size) = @_;
644
645 my $imagedir = $class->get_subdir($scfg, 'images');
646 $imagedir .= "/$vmid";
647
648 mkpath $imagedir;
649
650 $name = $class->find_free_diskname($imagedir, $scfg, $vmid, $fmt, 1) if !$name;
651
652 my (undef, $tmpfmt) = parse_name_dir($name);
653
654 die "illegal name '$name' - wrong extension for format ('$tmpfmt != '$fmt')\n"
655 if $tmpfmt ne $fmt;
656
657 my $path = "$imagedir/$name";
658
659 die "disk image '$path' already exists\n" if -e $path;
660
661 if ($fmt eq 'subvol') {
662 # only allow this if size = 0, so that user knows what he is doing
663 die "storage does not support subvol quotas\n" if $size != 0;
664
665 my $old_umask = umask(0022);
666 my $err;
667 mkdir($path) or $err = "unable to create subvol '$path' - $!\n";
668 umask $old_umask;
669 die $err if $err;
670 } else {
671 my $cmd = ['/usr/bin/qemu-img', 'create'];
672
673 push @$cmd, '-o', 'preallocation=metadata' if $fmt eq 'qcow2';
674
675 push @$cmd, '-f', $fmt, $path, "${size}K";
676
677 eval { run_command($cmd, errmsg => "unable to create image"); };
678 if ($@) {
679 unlink $path;
680 rmdir $imagedir;
681 die "$@";
682 }
683 }
684
685 return "$vmid/$name";
686 }
687
688 sub free_image {
689 my ($class, $storeid, $scfg, $volname, $isBase, $format) = @_;
690
691 my $path = $class->filesystem_path($scfg, $volname);
692
693 if ($isBase) {
694 # try to remove immutable flag
695 eval { run_command(['/usr/bin/chattr', '-i', $path]); };
696 warn $@ if $@;
697 }
698
699 if (defined($format) && ($format eq 'subvol')) {
700 File::Path::remove_tree($path);
701 } else {
702 if (!(-f $path || -l $path)) {
703 warn "disk image '$path' does not exist\n";
704 return undef;
705 }
706
707 unlink($path) || die "unlink '$path' failed - $!\n";
708 }
709
710 # try to cleanup directory to not clutter storage with empty $vmid dirs if
711 # all images from a guest got deleted
712 my $dir = dirname($path);
713 rmdir($dir);
714
715 return undef;
716 }
717
718 sub file_size_info {
719 my ($filename, $timeout) = @_;
720
721 my @fs = stat($filename);
722 my $mode = $fs[2];
723 my $ctime = $fs[10];
724
725 if (S_ISDIR($mode)) {
726 return wantarray ? (0, 'subvol', 0, undef, $ctime) : 1;
727 }
728
729 my $json = '';
730 eval {
731 run_command(['/usr/bin/qemu-img', 'info', '--output=json', $filename],
732 timeout => $timeout,
733 outfunc => sub { $json .= shift },
734 errfunc => sub { warn "$_[0]\n" }
735 );
736 };
737 warn $@ if $@;
738
739 my $info = eval { decode_json($json) };
740 warn "could not parse qemu-img info command output for '$filename'\n" if $@;
741
742 my ($size, $format, $used, $parent) = $info->@{qw(virtual-size format actual-size backing-filename)};
743
744 return wantarray ? ($size, $format, $used, $parent, $ctime) : $size;
745 }
746
747 sub volume_size_info {
748 my ($class, $scfg, $storeid, $volname, $timeout) = @_;
749 my $path = $class->filesystem_path($scfg, $volname);
750 return file_size_info($path, $timeout);
751
752 }
753
754 sub volume_resize {
755 my ($class, $scfg, $storeid, $volname, $size, $running) = @_;
756
757 die "can't resize this image format\n" if $volname !~ m/\.(raw|qcow2)$/;
758
759 return 1 if $running;
760
761 my $path = $class->filesystem_path($scfg, $volname);
762
763 my $format = ($class->parse_volname($volname))[6];
764
765 my $cmd = ['/usr/bin/qemu-img', 'resize', '-f', $format, $path , $size];
766
767 run_command($cmd, timeout => 10);
768
769 return undef;
770 }
771
772 sub volume_snapshot {
773 my ($class, $scfg, $storeid, $volname, $snap) = @_;
774
775 die "can't snapshot this image format\n" if $volname !~ m/\.(qcow2|qed)$/;
776
777 my $path = $class->filesystem_path($scfg, $volname);
778
779 my $cmd = ['/usr/bin/qemu-img', 'snapshot','-c', $snap, $path];
780
781 run_command($cmd);
782
783 return undef;
784 }
785
786 sub volume_rollback_is_possible {
787 my ($class, $scfg, $storeid, $volname, $snap) = @_;
788
789 return 1;
790 }
791
792 sub volume_snapshot_rollback {
793 my ($class, $scfg, $storeid, $volname, $snap) = @_;
794
795 die "can't rollback snapshot this image format\n" if $volname !~ m/\.(qcow2|qed)$/;
796
797 my $path = $class->filesystem_path($scfg, $volname);
798
799 my $cmd = ['/usr/bin/qemu-img', 'snapshot','-a', $snap, $path];
800
801 run_command($cmd);
802
803 return undef;
804 }
805
806 sub volume_snapshot_delete {
807 my ($class, $scfg, $storeid, $volname, $snap, $running) = @_;
808
809 die "can't delete snapshot for this image format\n" if $volname !~ m/\.(qcow2|qed)$/;
810
811 return 1 if $running;
812
813 my $path = $class->filesystem_path($scfg, $volname);
814
815 $class->deactivate_volume($storeid, $scfg, $volname, $snap, {});
816
817 my $cmd = ['/usr/bin/qemu-img', 'snapshot','-d', $snap, $path];
818
819 run_command($cmd);
820
821 return undef;
822 }
823
824 sub storage_can_replicate {
825 my ($class, $scfg, $storeid, $format) = @_;
826
827 return 0;
828 }
829
830 sub volume_has_feature {
831 my ($class, $scfg, $feature, $storeid, $volname, $snapname, $running) = @_;
832
833 my $features = {
834 snapshot => { current => { qcow2 => 1}, snap => { qcow2 => 1} },
835 clone => { base => {qcow2 => 1, raw => 1, vmdk => 1} },
836 template => { current => {qcow2 => 1, raw => 1, vmdk => 1, subvol => 1} },
837 copy => { base => {qcow2 => 1, raw => 1, vmdk => 1},
838 current => {qcow2 => 1, raw => 1, vmdk => 1},
839 snap => {qcow2 => 1} },
840 sparseinit => { base => {qcow2 => 1, raw => 1, vmdk => 1},
841 current => {qcow2 => 1, raw => 1, vmdk => 1} },
842 };
843
844 my ($vtype, $name, $vmid, $basename, $basevmid, $isBase, $format) =
845 $class->parse_volname($volname);
846
847 my $key = undef;
848 if($snapname){
849 $key = 'snap';
850 }else{
851 $key = $isBase ? 'base' : 'current';
852 }
853
854 return 1 if defined($features->{$feature}->{$key}->{$format});
855
856 return undef;
857 }
858
859 sub list_images {
860 my ($class, $storeid, $scfg, $vmid, $vollist, $cache) = @_;
861
862 my $imagedir = $class->get_subdir($scfg, 'images');
863
864 my ($defFmt, $vaidFmts) = default_format($scfg);
865 my $fmts = join ('|', @$vaidFmts);
866
867 my $res = [];
868
869 foreach my $fn (<$imagedir/[0-9][0-9]*/*>) {
870
871 next if $fn !~ m!^(/.+/(\d+)/([^/]+\.($fmts)))$!;
872 $fn = $1; # untaint
873
874 my $owner = $2;
875 my $name = $3;
876
877 next if !$vollist && defined($vmid) && ($owner ne $vmid);
878
879 my ($size, $format, $used, $parent, $ctime) = file_size_info($fn);
880 next if !($format && defined($size));
881
882 my $volid;
883 if ($parent && $parent =~ m!^../(\d+)/([^/]+\.($fmts))$!) {
884 my ($basevmid, $basename) = ($1, $2);
885 $volid = "$storeid:$basevmid/$basename/$owner/$name";
886 } else {
887 $volid = "$storeid:$owner/$name";
888 }
889
890 if ($vollist) {
891 my $found = grep { $_ eq $volid } @$vollist;
892 next if !$found;
893 }
894
895 my $info = {
896 volid => $volid, format => $format,
897 size => $size, vmid => $owner, used => $used, parent => $parent
898 };
899
900 $info->{ctime} = $ctime if $ctime;
901
902 push @$res, $info;
903 }
904
905 return $res;
906 }
907
908 # list templates ($tt = <iso|vztmpl|backup|snippets>)
909 my $get_subdir_files = sub {
910 my ($sid, $path, $tt, $vmid) = @_;
911
912 my $res = [];
913
914 foreach my $fn (<$path/*>) {
915
916 my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
917 $atime,$mtime,$ctime,$blksize,$blocks)
918 = stat($fn);
919
920 next if S_ISDIR($mode);
921
922 my $info;
923
924 if ($tt eq 'iso') {
925 next if $fn !~ m!/([^/]+$PVE::Storage::iso_extension_re)$!i;
926
927 $info = { volid => "$sid:iso/$1", format => 'iso' };
928
929 } elsif ($tt eq 'vztmpl') {
930 next if $fn !~ m!/([^/]+\.tar\.([gx]z))$!;
931
932 $info = { volid => "$sid:vztmpl/$1", format => "t$2" };
933
934 } elsif ($tt eq 'backup') {
935 next if defined($vmid) && $fn !~ m/\S+-$vmid-\S+/;
936 next if $fn !~ m!/([^/]+\.(tar|tar\.gz|tar\.lzo|tgz|vma|vma\.gz|vma\.lzo))$!;
937
938 my $format = $2;
939 $info = { volid => "$sid:backup/$1", format => $format };
940
941 if ($fn =~ m!^vzdump\-(?:lxc|qemu)\-(?:[1-9][0-9]{2,8})\-(\d{4})_(\d{2})_(\d{2})\-(\d{2})_(\d{2})_(\d{2})\.${format}$!) {
942 my $epoch = timelocal($6, $5, $4, $3, $2-1, $1 - 1900);
943 $info->{ctime} = $epoch;
944 }
945
946 if (defined($vmid) || $fn =~ m!\-([1-9][0-9]{2,8})\-[^/]+\.${format}$!) {
947 $info->{vmid} = $vmid // $1;
948 }
949
950
951 } elsif ($tt eq 'snippets') {
952
953 $info = {
954 volid => "$sid:snippets/". basename($fn),
955 format => 'snippet',
956 };
957 }
958
959 $info->{size} = $size;
960 $info->{ctime} //= $ctime;
961
962 push @$res, $info;
963 }
964
965 return $res;
966 };
967
968 sub list_volumes {
969 my ($class, $storeid, $scfg, $vmid, $content_types) = @_;
970
971 my $res = [];
972 my $vmlist = PVE::Cluster::get_vmlist();
973 foreach my $type (@$content_types) {
974 my $data;
975
976 if ($type eq 'images' || $type eq 'rootdir') {
977 $data = $class->list_images($storeid, $scfg, $vmid);
978 } elsif ($scfg->{path}) {
979 my $path = $class->get_subdir($scfg, $type);
980
981 if ($type eq 'iso' && !defined($vmid)) {
982 $data = $get_subdir_files->($storeid, $path, 'iso');
983 } elsif ($type eq 'vztmpl'&& !defined($vmid)) {
984 $data = $get_subdir_files->($storeid, $path, 'vztmpl');
985 } elsif ($type eq 'backup') {
986 $data = $get_subdir_files->($storeid, $path, 'backup', $vmid);
987 } elsif ($type eq 'snippets') {
988 $data = $get_subdir_files->($storeid, $path, 'snippets');
989 }
990 }
991
992 next if !$data;
993
994 foreach my $item (@$data) {
995 if ($type eq 'images' || $type eq 'rootdir') {
996 my $vminfo = $vmlist->{ids}->{$item->{vmid}};
997 my $vmtype;
998 if (defined($vminfo)) {
999 $vmtype = $vminfo->{type};
1000 }
1001 if (defined($vmtype) && $vmtype eq 'lxc') {
1002 $item->{content} = 'rootdir';
1003 } else {
1004 $item->{content} = 'images';
1005 }
1006 next if $type ne $item->{content};
1007 } else {
1008 $item->{content} = $type;
1009 }
1010
1011 push @$res, $item;
1012 }
1013 }
1014
1015 return $res;
1016 }
1017
1018 sub status {
1019 my ($class, $storeid, $scfg, $cache) = @_;
1020
1021 my $path = $scfg->{path};
1022
1023 die "storage definintion has no path\n" if !$path;
1024
1025 my $timeout = 2;
1026 my $res = PVE::Tools::df($path, $timeout);
1027
1028 return undef if !$res || !$res->{total};
1029
1030 return ($res->{total}, $res->{avail}, $res->{used}, 1);
1031 }
1032
1033 sub volume_snapshot_list {
1034 my ($class, $scfg, $storeid, $volname) = @_;
1035
1036 # implement in subclass
1037 die "Volume_snapshot_list is not implemented for $class";
1038
1039 # return an empty array if dataset does not exist.
1040 }
1041
1042 sub activate_storage {
1043 my ($class, $storeid, $scfg, $cache) = @_;
1044
1045 my $path = $scfg->{path};
1046
1047 die "storage definintion has no path\n" if !$path;
1048
1049 # this path test may hang indefinitely on unresponsive mounts
1050 my $timeout = 2;
1051 if (! PVE::Tools::run_fork_with_timeout($timeout, sub {-d $path})) {
1052 die "unable to activate storage '$storeid' - " .
1053 "directory '$path' does not exist or is unreachable\n";
1054 }
1055
1056
1057 return if defined($scfg->{mkdir}) && !$scfg->{mkdir};
1058
1059 if (defined($scfg->{content})) {
1060 foreach my $vtype (keys %$vtype_subdirs) {
1061 # OpenVZMigrate uses backup (dump) dir
1062 if (defined($scfg->{content}->{$vtype}) ||
1063 ($vtype eq 'backup' && defined($scfg->{content}->{'rootdir'}))) {
1064 my $subdir = $class->get_subdir($scfg, $vtype);
1065 mkpath $subdir if $subdir ne $path;
1066 }
1067 }
1068 }
1069 }
1070
1071 sub deactivate_storage {
1072 my ($class, $storeid, $scfg, $cache) = @_;
1073
1074 # do nothing by default
1075 }
1076
1077 sub map_volume {
1078 my ($class, $storeid, $scfg, $volname, $snapname) = @_;
1079
1080 my ($path) = $class->path($scfg, $volname, $storeid, $snapname);
1081 return $path;
1082 }
1083
1084 sub unmap_volume {
1085 my ($class, $storeid, $scfg, $volname, $snapname) = @_;
1086
1087 return 1;
1088 }
1089
1090 sub activate_volume {
1091 my ($class, $storeid, $scfg, $volname, $snapname, $cache) = @_;
1092
1093 my $path = $class->filesystem_path($scfg, $volname, $snapname);
1094
1095 # check is volume exists
1096 if ($scfg->{path}) {
1097 die "volume '$storeid:$volname' does not exist\n" if ! -e $path;
1098 } else {
1099 die "volume '$storeid:$volname' does not exist\n" if ! -b $path;
1100 }
1101 }
1102
1103 sub deactivate_volume {
1104 my ($class, $storeid, $scfg, $volname, $snapname, $cache) = @_;
1105
1106 # do nothing by default
1107 }
1108
1109 sub check_connection {
1110 my ($class, $storeid, $scfg) = @_;
1111 # do nothing by default
1112 return 1;
1113 }
1114
1115 # Import/Export interface:
1116 # Any path based storage is assumed to support 'raw' and 'tar' streams, so
1117 # the default implementations will return this if $scfg->{path} is set,
1118 # mimicking the old PVE::Storage::storage_migrate() function.
1119 #
1120 # Plugins may fall back to PVE::Storage::Plugin::volume_{export,import}...
1121 # functions in case the format doesn't match their specialized
1122 # implementations to reuse the raw/tar code.
1123 #
1124 # Format specification:
1125 # The following formats are all prefixed with image information in the form
1126 # of a 64 bit little endian unsigned integer (pack('Q<')) in order to be able
1127 # to preallocate the image on storages which require it.
1128 #
1129 # raw+size: (image files only)
1130 # A raw binary data stream such as produced via `dd if=TheImageFile`.
1131 # qcow2+size, vmdk: (image files only)
1132 # A raw qcow2/vmdk/... file such as produced via `dd if=some.qcow2` for
1133 # files which are already in qcow2 format, or via `qemu-img convert`.
1134 # Note that these formats are only valid with $with_snapshots being true.
1135 # tar+size: (subvolumes only)
1136 # A GNU tar stream containing just the inner contents of the subvolume.
1137 # This does not distinguish between the contents of a privileged or
1138 # unprivileged container. In other words, this is from the root user
1139 # namespace's point of view with no uid-mapping in effect.
1140 # As produced via `tar -C vm-100-disk-1.subvol -cpf TheOutputFile.dat .`
1141
1142 # Plugins may reuse these helpers. Changes to the header format should be
1143 # reflected by changes to the function prototypes.
1144 sub write_common_header($$) {
1145 my ($fh, $image_size_in_bytes) = @_;
1146 syswrite($fh, pack("Q<", $image_size_in_bytes), 8);
1147 }
1148
1149 sub read_common_header($) {
1150 my ($fh) = @_;
1151 sysread($fh, my $size, 8);
1152 $size = unpack('Q<', $size);
1153 die "import: no size found in export header, aborting.\n" if !defined($size);
1154 die "import: got a bad size (not a multiple of 1K), aborting.\n" if ($size&1023);
1155 # Size is in bytes!
1156 return $size;
1157 }
1158
1159 # Export a volume into a file handle as a stream of desired format.
1160 sub volume_export {
1161 my ($class, $scfg, $storeid, $fh, $volname, $format, $snapshot, $base_snapshot, $with_snapshots) = @_;
1162 if ($scfg->{path} && !defined($snapshot) && !defined($base_snapshot)) {
1163 my $file = $class->path($scfg, $volname, $storeid)
1164 or goto unsupported;
1165 my ($size, $file_format) = file_size_info($file);
1166
1167 if ($format eq 'raw+size') {
1168 goto unsupported if $with_snapshots || $file_format eq 'subvol';
1169 write_common_header($fh, $size);
1170 if ($file_format eq 'raw') {
1171 run_command(['dd', "if=$file", "bs=4k"], output => '>&'.fileno($fh));
1172 } else {
1173 run_command(['qemu-img', 'convert', '-f', $file_format, '-O', 'raw', $file, '/dev/stdout'],
1174 output => '>&'.fileno($fh));
1175 }
1176 return;
1177 } elsif ($format =~ /^(qcow2|vmdk)\+size$/) {
1178 my $data_format = $1;
1179 goto unsupported if !$with_snapshots || $file_format ne $data_format;
1180 write_common_header($fh, $size);
1181 run_command(['dd', "if=$file", "bs=4k"], output => '>&'.fileno($fh));
1182 return;
1183 } elsif ($format eq 'tar+size') {
1184 goto unsupported if $file_format ne 'subvol';
1185 write_common_header($fh, $size);
1186 run_command(['tar', @COMMON_TAR_FLAGS, '-cf', '-', '-C', $file, '.'],
1187 output => '>&'.fileno($fh));
1188 return;
1189 }
1190 }
1191 unsupported:
1192 die "volume export format $format not available for $class";
1193 }
1194
1195 sub volume_export_formats {
1196 my ($class, $scfg, $storeid, $volname, $snapshot, $base_snapshot, $with_snapshots) = @_;
1197 if ($scfg->{path} && !defined($snapshot) && !defined($base_snapshot)) {
1198 my $file = $class->path($scfg, $volname, $storeid)
1199 or return;
1200 my ($size, $format) = file_size_info($file);
1201
1202 if ($with_snapshots) {
1203 return ($format.'+size') if ($format eq 'qcow2' || $format eq 'vmdk');
1204 return ();
1205 }
1206 return ('tar+size') if $format eq 'subvol';
1207 return ('raw+size');
1208 }
1209 return ();
1210 }
1211
1212 # Import data from a stream, creating a new or replacing or adding to an existing volume.
1213 sub volume_import {
1214 my ($class, $scfg, $storeid, $fh, $volname, $format, $base_snapshot, $with_snapshots) = @_;
1215
1216 die "volume import format '$format' not available for $class\n"
1217 if $format !~ /^(raw|tar|qcow2|vmdk)\+size$/;
1218 my $data_format = $1;
1219
1220 die "format $format cannot be imported without snapshots\n"
1221 if !$with_snapshots && ($data_format eq 'qcow2' || $data_format eq 'vmdk');
1222 die "format $format cannot be imported with snapshots\n"
1223 if $with_snapshots && ($data_format eq 'raw' || $data_format eq 'tar');
1224
1225 my ($vtype, $name, $vmid, $basename, $basevmid, $isBase, $file_format) =
1226 $class->parse_volname($volname);
1227
1228 # XXX: Should we bother with conversion routines at this level? This won't
1229 # happen without manual CLI usage, so for now we just error out...
1230 die "cannot import format $format into a file of format $file_format\n"
1231 if $data_format ne $file_format && !($data_format eq 'tar' && $file_format eq 'subvol');
1232
1233 # Check for an existing file first since interrupting alloc_image doesn't
1234 # free it.
1235 my $file = $class->path($scfg, $volname, $storeid);
1236 die "file '$file' already exists\n" if -e $file;
1237
1238 my ($size) = read_common_header($fh);
1239 $size = int($size/1024);
1240
1241 eval {
1242 my $allocname = $class->alloc_image($storeid, $scfg, $vmid, $file_format, $name, $size);
1243 if ($allocname ne $volname) {
1244 my $oldname = $volname;
1245 $volname = $allocname; # Let the cleanup code know what to free
1246 die "internal error: unexpected allocated name: '$allocname' != '$oldname'\n";
1247 }
1248 my $file = $class->path($scfg, $volname, $storeid)
1249 or die "internal error: failed to get path to newly allocated volume $volname\n";
1250 if ($data_format eq 'raw' || $data_format eq 'qcow2' || $data_format eq 'vmdk') {
1251 run_command(['dd', "of=$file", 'conv=sparse', 'bs=64k'],
1252 input => '<&'.fileno($fh));
1253 } elsif ($data_format eq 'tar') {
1254 run_command(['tar', @COMMON_TAR_FLAGS, '-C', $file, '-xf', '-'],
1255 input => '<&'.fileno($fh));
1256 } else {
1257 die "volume import format '$format' not available for $class";
1258 }
1259 };
1260 if (my $err = $@) {
1261 eval { $class->free_image($storeid, $scfg, $volname, 0, $file_format) };
1262 warn $@ if $@;
1263 die $err;
1264 }
1265 }
1266
1267 sub volume_import_formats {
1268 my ($class, $scfg, $storeid, $volname, $base_snapshot, $with_snapshots) = @_;
1269 if ($scfg->{path} && !defined($base_snapshot)) {
1270 my $format = ($class->parse_volname($volname))[6];
1271 if ($with_snapshots) {
1272 return ($format.'+size') if ($format eq 'qcow2' || $format eq 'vmdk');
1273 return ();
1274 }
1275 return ('tar+size') if $format eq 'subvol';
1276 return ('raw+size');
1277 }
1278 return ();
1279 }
1280
1281 1;