+
+ $conf->{$opt}->{type} = 'veth';
+ $conf->{$opt}->{bridge} = $newnet->{bridge} if $newnet->{bridge};
+ $conf->{$opt}->{tag} = $newnet->{tag} if $newnet->{tag};
+ $conf->{$opt}->{firewall} = $newnet->{firewall} if $newnet->{firewall};
+ $conf->{$opt}->{hwaddr} = $newnet->{hwaddr} if $newnet->{hwaddr};
+ $conf->{$opt}->{name} = $newnet->{name} if $newnet->{name};
+ $conf->{$opt}->{'veth.pair'} = $newnet->{'veth.pair'} if $newnet->{'veth.pair'};
+
+ delete $conf->{$opt}->{ip} if $conf->{$opt}->{ip};
+ delete $conf->{$opt}->{ip6} if $conf->{$opt}->{ip6};
+ delete $conf->{$opt}->{gw} if $conf->{$opt}->{gw};
+ delete $conf->{$opt}->{gw6} if $conf->{$opt}->{gw6};
+
+ PVE::LXC::write_config($vmid, $conf);
+}
+
+sub update_ipconfig {
+ my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
+
+ my $lxc_setup = PVE::LXCSetup->new($conf, $rootdir);
+
+ my $optdata = $conf->{$opt};
+ my $deleted = [];
+ my $added = [];
+ my $netcmd = sub {
+ my $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', @_];
+ PVE::Tools::run_command($cmd);
+ };
+
+ my $change_ip_config = sub {
+ my ($ipversion) = @_;
+
+ my $family_opt = "-$ipversion";
+ my $suffix = $ipversion == 4 ? '' : $ipversion;
+ my $gw= "gw$suffix";
+ my $ip= "ip$suffix";
+
+ my $change_ip = &$safe_string_ne($optdata->{$ip}, $newnet->{$ip});
+ my $change_gw = &$safe_string_ne($optdata->{$gw}, $newnet->{$gw});
+
+ return if !$change_ip && !$change_gw;
+
+ # step 1: add new IP, if this fails we cancel
+ if ($change_ip && $newnet->{$ip}) {
+ eval { &$netcmd($family_opt, 'addr', 'add', $newnet->{$ip}, 'dev', $eth); };
+ if (my $err = $@) {
+ warn $err;
+ return;
+ }
+ }
+
+ # step 2: replace gateway
+ # If this fails we delete the added IP and cancel.
+ # If it succeeds we save the config and delete the old IP, ignoring
+ # errors. The config is then saved.
+ # Note: 'ip route replace' can add
+ if ($change_gw) {
+ if ($newnet->{$gw}) {
+ eval { &$netcmd($family_opt, 'route', 'replace', 'default', 'via', $newnet->{$gw}); };
+ if (my $err = $@) {
+ warn $err;
+ # the route was not replaced, the old IP is still available
+ # rollback (delete new IP) and cancel
+ if ($change_ip) {
+ eval { &$netcmd($family_opt, 'addr', 'del', $newnet->{$ip}, 'dev', $eth); };
+ warn $@ if $@; # no need to die here
+ }
+ return;
+ }
+ } else {
+ eval { &$netcmd($family_opt, 'route', 'del', 'default'); };
+ # if the route was not deleted, the guest might have deleted it manually
+ # warn and continue
+ warn $@ if $@;
+ }
+ }
+
+ # from this point on we safe the configuration
+ # step 3: delete old IP ignoring errors
+ if ($change_ip && $optdata->{$ip}) {
+ eval { &$netcmd($family_opt, 'addr', 'del', $optdata->{$ip}, 'dev', $eth); };
+ warn $@ if $@; # no need to die here
+ }
+
+ foreach my $property ($ip, $gw) {
+ if ($newnet->{$property}) {
+ $optdata->{$property} = $newnet->{$property};
+ } else {
+ delete $optdata->{$property};
+ }
+ }
+ PVE::LXC::write_config($vmid, $conf);
+ $lxc_setup->setup_network($conf);
+ };
+
+ &$change_ip_config(4);
+ &$change_ip_config(6);
+
+}
+
+# Internal snapshots
+
+# NOTE: Snapshot create/delete involves several non-atomic
+# action, and can take a long time.
+# So we try to avoid locking the file and use 'lock' variable
+# inside the config file instead.
+
+my $snapshot_copy_config = sub {
+ my ($source, $dest) = @_;
+
+ foreach my $k (keys %$source) {
+ next if $k eq 'snapshots';
+ next if $k eq 'pve.snapstate';
+ next if $k eq 'pve.snaptime';
+ next if $k eq 'pve.lock';
+ next if $k eq 'digest';
+ next if $k eq 'pve.comment';
+
+ $dest->{$k} = $source->{$k};
+ }
+};
+
+my $snapshot_prepare = sub {
+ my ($vmid, $snapname, $comment) = @_;
+
+ my $snap;
+
+ my $updatefn = sub {
+
+ my $conf = load_config($vmid);
+
+ check_lock($conf);
+
+ $conf->{'pve.lock'} = 'snapshot';
+
+ die "snapshot name '$snapname' already used\n"
+ if defined($conf->{snapshots}->{$snapname});
+
+ my $storecfg = PVE::Storage::config();
+ die "snapshot feature is not available\n" if !has_feature('snapshot', $conf, $storecfg);
+
+ $snap = $conf->{snapshots}->{$snapname} = {};
+
+ &$snapshot_copy_config($conf, $snap);
+
+ $snap->{'pve.snapstate'} = "prepare";
+ $snap->{'pve.snaptime'} = time();
+ $snap->{'pve.snapname'} = $snapname;
+ $snap->{'pve.snapcomment'} = $comment if $comment;
+ $conf->{snapshots}->{$snapname} = $snap;
+
+ PVE::LXC::write_config($vmid, $conf);
+ };
+
+ lock_container($vmid, 10, $updatefn);
+
+ return $snap;
+};
+
+my $snapshot_commit = sub {
+ my ($vmid, $snapname) = @_;
+
+ my $updatefn = sub {
+
+ my $conf = load_config($vmid);
+
+ die "missing snapshot lock\n"
+ if !($conf->{'pve.lock'} && $conf->{'pve.lock'} eq 'snapshot');
+
+ die "snapshot '$snapname' does not exist\n"
+ if !defined($conf->{snapshots}->{$snapname});
+
+ die "wrong snapshot state\n"
+ if !($conf->{snapshots}->{$snapname}->{'pve.snapstate'} && $conf->{snapshots}->{$snapname}->{'pve.snapstate'} eq "prepare");
+
+ delete $conf->{snapshots}->{$snapname}->{'pve.snapstate'};
+ delete $conf->{'pve.lock'};
+ $conf->{'pve.parent'} = $snapname;
+
+ PVE::LXC::write_config($vmid, $conf);
+
+ };
+
+ lock_container($vmid, 10 ,$updatefn);
+};
+
+sub has_feature {
+ my ($feature, $conf, $storecfg, $snapname) = @_;
+ #Fixme add other drives if necessary.
+ my $err;
+ my $volid = $conf->{'pve.volid'};
+ $err = 1 if !PVE::Storage::volume_has_feature($storecfg, $feature, $volid, $snapname);
+
+ return $err ? 0 : 1;
+}
+
+sub snapshot_create {
+ my ($vmid, $snapname, $comment) = @_;
+
+ my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
+
+ my $config = load_config($vmid);
+
+ my $cmd = "/usr/bin/lxc-freeze -n $vmid";
+ my $running = check_running($vmid);
+ eval {
+ if ($running) {
+ PVE::Tools::run_command($cmd);
+ };
+
+ my $storecfg = PVE::Storage::config();
+ my $volid = $config->{'pve.volid'};
+
+ $cmd = "/usr/bin/lxc-unfreeze -n $vmid";
+ if ($running) {
+ PVE::Tools::run_command($cmd);
+ };
+
+ PVE::Storage::volume_snapshot($storecfg, $volid, $snapname);
+ &$snapshot_commit($vmid, $snapname);
+ };
+ if(my $err = $@) {
+ snapshot_delete($vmid, $snapname, 1);
+ die "$err\n";
+ }
+}
+
+sub snapshot_delete {
+ my ($vmid, $snapname, $force) = @_;
+
+ my $snap;
+
+ my $conf;
+
+ my $updatefn = sub {
+
+ $conf = load_config($vmid);
+
+ $snap = $conf->{snapshots}->{$snapname};
+
+ check_lock($conf);
+
+ die "snapshot '$snapname' does not exist\n" if !defined($snap);
+
+ $snap->{'pve.snapstate'} = 'delete';
+
+ PVE::LXC::write_config($vmid, $conf);
+ };
+
+ lock_container($vmid, 10, $updatefn);
+
+ my $storecfg = PVE::Storage::config();
+
+ my $del_snap = sub {
+
+ check_lock($conf);
+
+ if ($conf->{'pve.parent'} eq $snapname) {
+ if ($conf->{snapshots}->{$snapname}->{'pve.snapname'}) {
+ $conf->{'pve.parent'} = $conf->{snapshots}->{$snapname}->{'pve.parent'};
+ } else {
+ delete $conf->{'pve.parent'};
+ }
+ }
+
+ delete $conf->{snapshots}->{$snapname};
+
+ PVE::LXC::write_config($vmid, $conf);
+ };
+
+ my $volid = $conf->{snapshots}->{$snapname}->{'pve.volid'};
+
+ eval {
+ PVE::Storage::volume_snapshot_delete($storecfg, $volid, $snapname);
+ };
+ my $err = $@;
+
+ if(!$err || ($err && $force)) {
+ lock_container($vmid, 10, $del_snap);
+ if ($err) {
+ die "Can't delete snapshot: $vmid $snapname $err\n";
+ }
+ }
+}
+
+sub snapshot_rollback {
+ my ($vmid, $snapname) = @_;
+
+ my $storecfg = PVE::Storage::config();
+
+ my $conf = load_config($vmid);
+
+ my $snap = $conf->{snapshots}->{$snapname};
+
+ die "snapshot '$snapname' does not exist\n" if !defined($snap);
+
+ PVE::Storage::volume_rollback_is_possible($storecfg, $snap->{'pve.volid'},
+ $snapname);
+
+ my $updatefn = sub {
+
+ die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n" if $snap->{snapstate};
+
+ check_lock($conf);
+
+ system("lxc-stop -n $vmid --kill") if check_running($vmid);
+
+ die "unable to rollback vm $vmid: vm is running\n"
+ if check_running($vmid);
+
+ $conf->{'pve.lock'} = 'rollback';
+
+ my $forcemachine;
+
+ # copy snapshot config to current config
+
+ my $tmp_conf = $conf;
+ &$snapshot_copy_config($tmp_conf->{snapshots}->{$snapname}, $conf);
+ $conf->{snapshots} = $tmp_conf->{snapshots};
+ delete $conf->{'pve.snaptime'};
+ delete $conf->{'pve.snapname'};
+ $conf->{'pve.parent'} = $snapname;
+
+ PVE::LXC::write_config($vmid, $conf);
+ };
+
+ my $unlockfn = sub {
+ delete $conf->{'pve.lock'};
+ PVE::LXC::write_config($vmid, $conf);
+ };
+
+ lock_container($vmid, 10, $updatefn);
+
+ PVE::Storage::volume_snapshot_rollback($storecfg, $conf->{'pve.volid'}, $snapname);
+
+ lock_container($vmid, 5, $unlockfn);