]> git.proxmox.com Git - pve-guest-common.git/blob - PVE/AbstractConfig.pm
b63a744dc0f5801c2cf2157db7e42ae998a747db
[pve-guest-common.git] / PVE / AbstractConfig.pm
1 package PVE::AbstractConfig;
2
3 use strict;
4 use warnings;
5
6 use PVE::Tools qw(lock_file lock_file_full);
7 use PVE::INotify;
8 use PVE::Cluster;
9 use PVE::Storage;
10
11 use PVE::ReplicationConfig;
12 use PVE::Replication;
13
14 my $nodename = PVE::INotify::nodename();
15
16 # Printable string, currently either "VM" or "CT"
17 sub guest_type {
18 my ($class) = @_;
19 die "abstract method - implement me ";
20 }
21
22 sub __config_max_unused_disks {
23 my ($class) = @_;
24
25 die "implement me - abstract method\n";
26 }
27
28 # Path to the flock file for this VM/CT
29 sub config_file_lock {
30 my ($class, $vmid) = @_;
31 die "abstract method - implement me";
32 }
33
34 # Relative config file path for this VM/CT in CFS
35 sub cfs_config_path {
36 my ($class, $vmid, $node) = @_;
37 die "abstract method - implement me";
38 }
39
40 # Absolute config file path for this VM/CT
41 sub config_file {
42 my ($class, $vmid, $node) = @_;
43
44 my $cfspath = $class->cfs_config_path($vmid, $node);
45 return "/etc/pve/$cfspath";
46 }
47
48 # Read and parse config file for this VM/CT
49 sub load_config {
50 my ($class, $vmid, $node) = @_;
51
52 $node = $nodename if !$node;
53 my $cfspath = $class->cfs_config_path($vmid, $node);
54
55 my $conf = PVE::Cluster::cfs_read_file($cfspath);
56 die "Configuration file '$cfspath' does not exist\n"
57 if !defined($conf);
58
59 return $conf;
60 }
61
62 # Generate and write config file for this VM/CT
63 sub write_config {
64 my ($class, $vmid, $conf) = @_;
65
66 my $cfspath = $class->cfs_config_path($vmid);
67
68 PVE::Cluster::cfs_write_file($cfspath, $conf);
69 }
70
71 # Pending changes related
72
73 sub parse_pending_delete {
74 my ($class, $data) = @_;
75
76 return {} if !$data;
77
78 $data =~ s/[,;]/ /g;
79 $data =~ s/^\s+//;
80
81 my $pending_deletions = {};
82 for my $entry (split(/\s+/, $data)) {
83 my ($force, $key) = $entry =~ /^(!?)(.*)$/;
84
85 $pending_deletions->{$key} = {
86 force => $force ? 1 : 0,
87 };
88 }
89
90 return $pending_deletions;
91 }
92
93 sub print_pending_delete {
94 my ($class, $delete_hash) = @_;
95
96 my $render_key = sub {
97 my $key = shift;
98 $key = "!$key" if $delete_hash->{$key}->{force};
99 return $key;
100 };
101
102 join (',', map { $render_key->($_) } sort keys %$delete_hash);
103 }
104
105 sub add_to_pending_delete {
106 my ($class, $conf, $key, $force) = @_;
107
108 $conf->{pending} //= {};
109 my $pending = $conf->{pending};
110 my $pending_delete_hash = $class->parse_pending_delete($pending->{delete});
111
112 $pending_delete_hash->{$key} = { force => $force };
113
114 $pending->{delete} = $class->print_pending_delete($pending_delete_hash);
115
116 return $conf;
117 }
118
119 sub remove_from_pending_delete {
120 my ($class, $conf, $key) = @_;
121
122 my $pending = $conf->{pending};
123 my $pending_delete_hash = $class->parse_pending_delete($pending->{delete});
124
125 return $conf if ! exists $pending_delete_hash->{$key};
126
127 delete $pending_delete_hash->{$key};
128
129 if (%$pending_delete_hash) {
130 $pending->{delete} = $class->print_pending_delete($pending_delete_hash);
131 } else {
132 delete $pending->{delete};
133 }
134
135 return $conf;
136 }
137
138 sub cleanup_pending {
139 my ($class, $conf) = @_;
140
141 my $pending = $conf->{pending};
142 # remove pending changes when nothing changed
143 my $changes;
144 foreach my $opt (keys %{$conf->{pending}}) {
145 next if $opt eq 'delete'; # just to be sure
146 if (defined($conf->{$opt}) && ($pending->{$opt} eq $conf->{$opt})) {
147 $changes = 1;
148 delete $pending->{$opt};
149 }
150 }
151
152 my $current_delete_hash = $class->parse_pending_delete($pending->{delete});
153 my $pending_delete_hash = {};
154 for my $opt (keys %$current_delete_hash) {
155 if (defined($conf->{$opt})) {
156 $pending_delete_hash->{$opt} = $current_delete_hash->{$opt};
157 } else {
158 $changes = 1;
159 }
160 }
161
162 if (%$pending_delete_hash) {
163 $pending->{delete} = $class->print_pending_delete($pending_delete_hash);
164 } else {
165 delete $pending->{delete};
166 }
167
168 return $changes;
169 }
170
171 sub load_snapshot_config {
172 my ($class, $vmid, $snapname) = @_;
173
174 my $conf = $class->load_config($vmid);
175
176 my $snapshot = $conf->{snapshots}->{$snapname};
177 die "snapshot '$snapname' does not exist\n" if !defined($snapshot);
178
179 $snapshot->{digest} = $conf->{digest};
180
181 return $snapshot;
182
183 }
184
185 sub load_current_config {
186 my ($class, $vmid, $current) = @_;
187
188 my $conf = $class->load_config($vmid);
189
190 # take pending changes in
191 if (!$current) {
192 foreach my $opt (keys %{$conf->{pending}}) {
193 next if $opt eq 'delete';
194 my $value = $conf->{pending}->{$opt};
195 next if ref($value); # just to be sure
196 $conf->{$opt} = $value;
197 }
198 my $pending_delete_hash = $class->parse_pending_delete($conf->{pending}->{delete});
199 foreach my $opt (keys %$pending_delete_hash) {
200 delete $conf->{$opt} if $conf->{$opt};
201 }
202 }
203
204 delete $conf->{snapshots};
205 delete $conf->{pending};
206
207 return $conf;
208 }
209
210
211 # Lock config file using flock, run $code with @param, unlock config file.
212 # $timeout is the maximum time to aquire the flock
213 sub lock_config_full {
214 my ($class, $vmid, $timeout, $code, @param) = @_;
215
216 my $filename = $class->config_file_lock($vmid);
217
218 my $res = lock_file($filename, $timeout, $code, @param);
219
220 die $@ if $@;
221
222 return $res;
223 }
224
225 sub create_and_lock_config {
226 my ($class, $vmid, $allow_existing, $lock) = @_;
227
228 $class->lock_config_full($vmid, 5, sub {
229 PVE::Cluster::check_vmid_unused($vmid, $allow_existing);
230
231 my $conf = eval { $class->load_config($vmid) } || {};
232 $class->check_lock($conf);
233 $conf->{lock} = $lock // 'create';
234 $class->write_config($vmid, $conf);
235 });
236 }
237
238 # destroys configuration, only applyable for configs owned by the callers node.
239 # dies if removal fails, e.g., when inquorate.
240 sub destroy_config {
241 my ($class, $vmid) = @_;
242
243 my $config_fn = $class->config_file($vmid, $nodename);
244 unlink $config_fn or die "failed to remove config file: $!\n";
245 }
246
247 # Lock config file using flock, run $code with @param, unlock config file.
248 # $timeout is the maximum time to aquire the flock
249 # $shared eq 1 creates a non-exclusive ("read") flock
250 sub lock_config_mode {
251 my ($class, $vmid, $timeout, $shared, $code, @param) = @_;
252
253 my $filename = $class->config_file_lock($vmid);
254
255 my $res = lock_file_full($filename, $timeout, $shared, $code, @param);
256
257 die $@ if $@;
258
259 return $res;
260 }
261
262 # Lock config file using flock, run $code with @param, unlock config file.
263 sub lock_config {
264 my ($class, $vmid, $code, @param) = @_;
265
266 return $class->lock_config_full($vmid, 10, $code, @param);
267 }
268
269 # Checks whether the config is locked with the lock parameter
270 sub check_lock {
271 my ($class, $conf) = @_;
272
273 die $class->guest_type()." is locked ($conf->{lock})\n" if $conf->{lock};
274 }
275
276 # Returns whether the config is locked with the lock parameter, also checks
277 # whether the lock value is correct if the optional $lock is set.
278 sub has_lock {
279 my ($class, $conf, $lock) = @_;
280
281 return $conf->{lock} && (!defined($lock) || $lock eq $conf->{lock});
282 }
283
284 # Sets the lock parameter for this VM/CT's config to $lock.
285 sub set_lock {
286 my ($class, $vmid, $lock) = @_;
287
288 my $conf;
289 $class->lock_config($vmid, sub {
290 $conf = $class->load_config($vmid);
291 $class->check_lock($conf);
292 $conf->{lock} = $lock;
293 $class->write_config($vmid, $conf);
294 });
295 return $conf;
296 }
297
298 # Removes the lock parameter for this VM/CT's config, also checks whether
299 # the lock value is correct if the optional $lock is set.
300 sub remove_lock {
301 my ($class, $vmid, $lock) = @_;
302
303 $class->lock_config($vmid, sub {
304 my $conf = $class->load_config($vmid);
305 if (!$conf->{lock}) {
306 my $lockstring = defined($lock) ? "'$lock' " : "any";
307 die "no lock found trying to remove $lockstring lock\n";
308 } elsif (defined($lock) && $conf->{lock} ne $lock) {
309 die "found lock '$conf->{lock}' trying to remove '$lock' lock\n";
310 }
311 delete $conf->{lock};
312 $class->write_config($vmid, $conf);
313 });
314 }
315
316 # Checks whether protection mode is enabled for this VM/CT.
317 sub check_protection {
318 my ($class, $conf, $err_msg) = @_;
319
320 if ($conf->{protection}) {
321 die "$err_msg - protection mode enabled\n";
322 }
323 }
324
325 # Adds an unused volume to $config, if possible.
326 sub add_unused_volume {
327 my ($class, $config, $volid) = @_;
328
329 my $key;
330 for (my $ind = $class->__config_max_unused_disks() - 1; $ind >= 0; $ind--) {
331 my $test = "unused$ind";
332 if (my $vid = $config->{$test}) {
333 return if $vid eq $volid; # do not add duplicates
334 } else {
335 $key = $test;
336 }
337 }
338
339 die "Too many unused volumes - please delete them first.\n" if !$key;
340
341 $config->{$key} = $volid;
342
343 return $key;
344 }
345
346 # Returns whether the template parameter is set in $conf.
347 sub is_template {
348 my ($class, $conf) = @_;
349
350 return 1 if defined $conf->{template} && $conf->{template} == 1;
351 }
352
353 # Checks whether $feature is availabe for the referenced volumes in $conf.
354 # Note: depending on the parameters, some volumes may be skipped!
355 sub has_feature {
356 my ($class, $feature, $conf, $storecfg, $snapname, $running, $backup_only) = @_;
357 die "implement me - abstract method\n";
358 }
359
360 # get all replicatable volume (hash $res->{$volid} = 1)
361 # $cleanup: for cleanup - simply ignores volumes without replicate feature
362 # $norerr: never raise exceptions - return undef instead
363 sub get_replicatable_volumes {
364 my ($class, $storecfg, $vmid, $conf, $cleanup, $noerr) = @_;
365
366 die "implement me - abstract method\n";
367 }
368
369 # Internal snapshots
370
371 # NOTE: Snapshot create/delete involves several non-atomic
372 # actions, and can take a long time.
373 # So we try to avoid locking the file and use the 'lock' variable
374 # inside the config file instead.
375
376 # Save the vmstate (RAM).
377 sub __snapshot_save_vmstate {
378 my ($class, $vmid, $conf, $snapname, $storecfg) = @_;
379 die "implement me - abstract method\n";
380 }
381
382 # Check whether the VM/CT is running.
383 sub __snapshot_check_running {
384 my ($class, $vmid) = @_;
385 die "implement me - abstract method\n";
386 }
387
388 # Check whether we need to freeze the VM/CT
389 sub __snapshot_check_freeze_needed {
390 my ($sself, $vmid, $config, $save_vmstate) = @_;
391 die "implement me - abstract method\n";
392 }
393
394 # Freeze or unfreeze the VM/CT.
395 sub __snapshot_freeze {
396 my ($class, $vmid, $unfreeze) = @_;
397
398 die "abstract method - implement me\n";
399 }
400
401 # Code run before and after creating all the volume snapshots
402 # base: noop
403 sub __snapshot_create_vol_snapshots_hook {
404 my ($class, $vmid, $snap, $running, $hook) = @_;
405
406 return;
407 }
408
409 # Create the volume snapshots for the VM/CT.
410 sub __snapshot_create_vol_snapshot {
411 my ($class, $vmid, $vs, $volume, $snapname) = @_;
412
413 die "abstract method - implement me\n";
414 }
415
416 # Remove a drive from the snapshot config.
417 sub __snapshot_delete_remove_drive {
418 my ($class, $snap, $drive) = @_;
419
420 die "abstract method - implement me\n";
421 }
422
423 # Delete the vmstate file/drive
424 sub __snapshot_delete_vmstate_file {
425 my ($class, $snap, $force) = @_;
426
427 die "abstract method - implement me\n";
428 }
429
430 # Delete a volume snapshot
431 sub __snapshot_delete_vol_snapshot {
432 my ($class, $vmid, $vs, $volume, $snapname) = @_;
433
434 die "abstract method - implement me\n";
435 }
436
437 # called during rollback prepare, and between config rollback and starting guest
438 # can change config, e.g. for vmgenid
439 # $data is shared across calls and passed to vm_start
440 sub __snapshot_rollback_hook {
441 my ($class, $vmid, $conf, $snap, $prepare, $data) = @_;
442
443 return;
444 }
445
446 # Checks whether a volume snapshot is possible for this volume.
447 sub __snapshot_rollback_vol_possible {
448 my ($class, $volume, $snapname) = @_;
449
450 die "abstract method - implement me\n";
451 }
452
453 # Rolls back this volume.
454 sub __snapshot_rollback_vol_rollback {
455 my ($class, $volume, $snapname) = @_;
456
457 die "abstract method - implement me\n";
458 }
459
460 # Stops the VM/CT for a rollback.
461 sub __snapshot_rollback_vm_stop {
462 my ($class, $vmid) = @_;
463
464 die "abstract method - implement me\n";
465 }
466
467 # Start the VM/CT after a rollback with restored vmstate.
468 sub __snapshot_rollback_vm_start {
469 my ($class, $vmid, $vmstate, $data);
470
471 die "abstract method - implement me\n";
472 }
473
474 # Get list of volume IDs which are referenced in $conf, but not in $snap.
475 sub __snapshot_rollback_get_unused {
476 my ($class, $conf, $snap) = @_;
477
478 die "abstract method - implement me\n";
479 }
480
481 # Iterate over all configured volumes, calling $func for each key/value pair.
482 sub __snapshot_foreach_volume {
483 my ($class, $conf, $func) = @_;
484
485 die "abstract method - implement me\n";
486 }
487
488 # Copy the current config $source to the snapshot config $dest
489 sub __snapshot_copy_config {
490 my ($class, $source, $dest) = @_;
491
492 foreach my $k (keys %$source) {
493 next if $k eq 'snapshots';
494 next if $k eq 'snapstate';
495 next if $k eq 'snaptime';
496 next if $k eq 'vmstate';
497 next if $k eq 'lock';
498 next if $k eq 'digest';
499 next if $k eq 'description';
500 next if $k =~ m/^unused\d+$/;
501
502 $dest->{$k} = $source->{$k};
503 }
504 };
505
506 # Apply the snapshot config $snap to the config $conf (rollback)
507 sub __snapshot_apply_config {
508 my ($class, $conf, $snap) = @_;
509
510 # copy snapshot list
511 my $newconf = {
512 snapshots => $conf->{snapshots},
513 };
514
515 # keep description and list of unused disks
516 foreach my $k (keys %$conf) {
517 next if !($k =~ m/^unused\d+$/ || $k eq 'description');
518
519 $newconf->{$k} = $conf->{$k};
520 }
521
522 $class->__snapshot_copy_config($snap, $newconf);
523
524 return $newconf;
525 }
526
527 # Prepares the configuration for snapshotting.
528 sub __snapshot_prepare {
529 my ($class, $vmid, $snapname, $save_vmstate, $comment) = @_;
530
531 my $snap;
532
533 my $updatefn = sub {
534
535 my $conf = $class->load_config($vmid);
536
537 die "you can't take a snapshot if it's a template\n"
538 if $class->is_template($conf);
539
540 $class->check_lock($conf);
541
542 $conf->{lock} = 'snapshot';
543
544 die "snapshot name '$snapname' already used\n"
545 if defined($conf->{snapshots}->{$snapname});
546
547 my $storecfg = PVE::Storage::config();
548 die "snapshot feature is not available\n"
549 if !$class->has_feature('snapshot', $conf, $storecfg, undef, undef, $snapname eq 'vzdump');
550
551 $snap = $conf->{snapshots}->{$snapname} = {};
552
553 if ($save_vmstate && $class->__snapshot_check_running($vmid)) {
554 $class->__snapshot_save_vmstate($vmid, $conf, $snapname, $storecfg);
555 }
556
557 $class->__snapshot_copy_config($conf, $snap);
558
559 $snap->{snapstate} = "prepare";
560 $snap->{snaptime} = time();
561 $snap->{description} = $comment if $comment;
562
563 $class->write_config($vmid, $conf);
564 };
565
566 $class->lock_config($vmid, $updatefn);
567
568 return $snap;
569 }
570
571 # Commits the configuration after snapshotting.
572 sub __snapshot_commit {
573 my ($class, $vmid, $snapname) = @_;
574
575 my $updatefn = sub {
576
577 my $conf = $class->load_config($vmid);
578
579 die "missing snapshot lock\n"
580 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
581
582 my $snap = $conf->{snapshots}->{$snapname};
583 die "snapshot '$snapname' does not exist\n" if !defined($snap);
584
585 die "wrong snapshot state\n"
586 if !($snap->{snapstate} && $snap->{snapstate} eq "prepare");
587
588 delete $snap->{snapstate};
589 delete $conf->{lock};
590
591 $conf->{parent} = $snapname;
592
593 $class->write_config($vmid, $conf);
594 };
595
596 $class->lock_config($vmid, $updatefn);
597 }
598
599 # Creates a snapshot for the VM/CT.
600 sub snapshot_create {
601 my ($class, $vmid, $snapname, $save_vmstate, $comment) = @_;
602
603 my $snap = $class->__snapshot_prepare($vmid, $snapname, $save_vmstate, $comment);
604
605 $save_vmstate = 0 if !$snap->{vmstate};
606
607 my $conf = $class->load_config($vmid);
608
609 my ($running, $freezefs) = $class->__snapshot_check_freeze_needed($vmid, $conf, $snap->{vmstate});
610
611 my $drivehash = {};
612
613 eval {
614 if ($freezefs) {
615 $class->__snapshot_freeze($vmid, 0);
616 }
617
618 $class->__snapshot_create_vol_snapshots_hook($vmid, $snap, $running, "before");
619
620 $class->__snapshot_foreach_volume($snap, sub {
621 my ($vs, $volume) = @_;
622
623 $class->__snapshot_create_vol_snapshot($vmid, $vs, $volume, $snapname);
624 $drivehash->{$vs} = 1;
625 });
626 };
627 my $err = $@;
628
629 if ($running) {
630 $class->__snapshot_create_vol_snapshots_hook($vmid, $snap, $running, "after");
631 if ($freezefs) {
632 $class->__snapshot_freeze($vmid, 1);
633 }
634 $class->__snapshot_create_vol_snapshots_hook($vmid, $snap, $running, "after-unfreeze");
635 }
636
637 if ($err) {
638 warn "snapshot create failed: starting cleanup\n";
639 eval { $class->snapshot_delete($vmid, $snapname, 1, $drivehash); };
640 warn "$@" if $@;
641 die "$err\n";
642 }
643
644 $class->__snapshot_commit($vmid, $snapname);
645 }
646
647 # Deletes a snapshot.
648 # Note: $drivehash is only set when called from snapshot_create.
649 sub snapshot_delete {
650 my ($class, $vmid, $snapname, $force, $drivehash) = @_;
651
652 my $prepare = 1;
653
654 my $unused = [];
655
656 my $conf = $class->load_config($vmid);
657 my $snap = $conf->{snapshots}->{$snapname};
658
659 die "snapshot '$snapname' does not exist\n" if !defined($snap);
660
661 $class->set_lock($vmid, 'snapshot-delete')
662 if (!$drivehash); # doesn't already have a 'snapshot' lock
663
664 my $unlink_parent = sub {
665 my ($confref, $new_parent) = @_;
666
667 if ($confref->{parent} && $confref->{parent} eq $snapname) {
668 if ($new_parent) {
669 $confref->{parent} = $new_parent;
670 } else {
671 delete $confref->{parent};
672 }
673 }
674 };
675
676 my $remove_drive = sub {
677 my ($drive) = @_;
678
679 my $conf = $class->load_config($vmid);
680 $snap = $conf->{snapshots}->{$snapname};
681 die "snapshot '$snapname' does not exist\n" if !defined($snap);
682
683 $class->__snapshot_delete_remove_drive($snap, $drive);
684
685 $class->write_config($vmid, $conf);
686 };
687
688 #prepare
689 $class->lock_config($vmid, sub {
690 my $conf = $class->load_config($vmid);
691
692 die "you can't delete a snapshot if vm is a template\n"
693 if $class->is_template($conf);
694
695 $snap = $conf->{snapshots}->{$snapname};
696 die "snapshot '$snapname' does not exist\n" if !defined($snap);
697
698 $snap->{snapstate} = 'delete';
699
700 $class->write_config($vmid, $conf);
701 });
702
703 # now remove vmstate file
704 if ($snap->{vmstate}) {
705 $class->__snapshot_delete_vmstate_file($snap, $force);
706
707 # save changes (remove vmstate from snapshot)
708 $class->lock_config($vmid, $remove_drive, 'vmstate') if !$force;
709 };
710
711 # now remove all volume snapshots
712 $class->__snapshot_foreach_volume($snap, sub {
713 my ($vs, $volume) = @_;
714
715 return if $snapname eq 'vzdump' && $vs ne 'rootfs' && !$volume->{backup};
716 if (!$drivehash || $drivehash->{$vs}) {
717 eval { $class->__snapshot_delete_vol_snapshot($vmid, $vs, $volume, $snapname, $unused); };
718 if (my $err = $@) {
719 die $err if !$force;
720 warn $err;
721 }
722 }
723
724 # save changes (remove drive from snapshot)
725 $class->lock_config($vmid, $remove_drive, $vs) if !$force;
726 });
727
728 # now cleanup config
729 $class->lock_config($vmid, sub {
730 my $conf = $class->load_config($vmid);
731 $snap = $conf->{snapshots}->{$snapname};
732 die "snapshot '$snapname' does not exist\n" if !defined($snap);
733
734 # remove parent refs
735 &$unlink_parent($conf, $snap->{parent});
736 foreach my $sn (keys %{$conf->{snapshots}}) {
737 next if $sn eq $snapname;
738 &$unlink_parent($conf->{snapshots}->{$sn}, $snap->{parent});
739 }
740
741
742 delete $conf->{snapshots}->{$snapname};
743 delete $conf->{lock};
744 foreach my $volid (@$unused) {
745 $class->add_unused_volume($conf, $volid);
746 }
747
748 $class->write_config($vmid, $conf);
749 });
750 }
751
752 # Rolls back to a given snapshot.
753 sub snapshot_rollback {
754 my ($class, $vmid, $snapname) = @_;
755
756 my $prepare = 1;
757
758 my $storecfg = PVE::Storage::config();
759
760 my $conf = $class->load_config($vmid);
761
762 my $get_snapshot_config = sub {
763
764 die "you can't rollback if vm is a template\n" if $class->is_template($conf);
765
766 my $res = $conf->{snapshots}->{$snapname};
767
768 die "snapshot '$snapname' does not exist\n" if !defined($res);
769
770 return $res;
771 };
772
773 my $repl_conf = PVE::ReplicationConfig->new();
774 if ($repl_conf->check_for_existing_jobs($vmid, 1)) {
775 # remove all replication snapshots
776 my $volumes = $class->get_replicatable_volumes($storecfg, $vmid, $conf, 1);
777 my $sorted_volids = [ sort keys %$volumes ];
778
779 # remove all local replication snapshots (jobid => undef)
780 my $logfunc = sub { my $line = shift; chomp $line; print "$line\n"; };
781 PVE::Replication::prepare($storecfg, $sorted_volids, undef, 0, undef, $logfunc);
782 }
783
784 my $snap = &$get_snapshot_config();
785
786 $class->__snapshot_foreach_volume($snap, sub {
787 my ($vs, $volume) = @_;
788
789 $class->__snapshot_rollback_vol_possible($volume, $snapname);
790 });
791
792 my $data = {};
793
794 my $updatefn = sub {
795
796 $conf = $class->load_config($vmid);
797
798 $snap = &$get_snapshot_config();
799
800 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
801 if $snap->{snapstate};
802
803 if ($prepare) {
804 $class->check_lock($conf);
805 $class->__snapshot_rollback_vm_stop($vmid);
806 }
807
808 die "unable to rollback vm $vmid: vm is running\n"
809 if $class->__snapshot_check_running($vmid);
810
811 if ($prepare) {
812 $conf->{lock} = 'rollback';
813 } else {
814 die "got wrong lock\n" if !($conf->{lock} && $conf->{lock} eq 'rollback');
815 delete $conf->{lock};
816
817 my $unused = $class->__snapshot_rollback_get_unused($conf, $snap);
818
819 foreach my $volid (@$unused) {
820 $class->add_unused_volume($conf, $volid);
821 }
822
823 # copy snapshot config to current config
824 $conf = $class->__snapshot_apply_config($conf, $snap);
825 $conf->{parent} = $snapname;
826 }
827
828 $class->__snapshot_rollback_hook($vmid, $conf, $snap, $prepare, $data);
829
830 $class->write_config($vmid, $conf);
831
832 if (!$prepare && $snap->{vmstate}) {
833 $class->__snapshot_rollback_vm_start($vmid, $snap->{vmstate}, $data);
834 }
835 };
836
837 $class->lock_config($vmid, $updatefn);
838
839 $class->__snapshot_foreach_volume($snap, sub {
840 my ($vs, $volume) = @_;
841
842 $class->__snapshot_rollback_vol_rollback($volume, $snapname);
843 });
844
845 $prepare = 0;
846 $class->lock_config($vmid, $updatefn);
847 }
848
849 # bash completion helper
850
851 sub snapshot_list {
852 my ($class, $vmid) = @_;
853
854 my $snapshot = eval {
855 my $conf = $class->load_config($vmid);
856 my $snapshots = $conf->{snapshots} || [];
857 [ sort keys %$snapshots ]
858 } || [];
859
860 return $snapshot;
861 }
862
863 1;