+sub vm_start {
+ my ($vmid, $conf, $skiplock) = @_;
+
+ # apply pending changes while starting
+ if (scalar(keys %{$conf->{pending}})) {
+ my $storecfg = PVE::Storage::config();
+ PVE::LXC::Config->vmconfig_apply_pending($vmid, $conf, $storecfg);
+ $conf = PVE::LXC::Config->load_config($vmid); # update/reload
+ }
+
+ update_lxc_config($vmid, $conf);
+
+ my $skiplock_flag_fn = "/run/lxc/skiplock-$vmid";
+
+ if ($skiplock) {
+ open(my $fh, '>', $skiplock_flag_fn) || die "failed to open $skiplock_flag_fn for writing: $!\n";
+ close($fh);
+ }
+
+ my $cmd = ['systemctl', 'start', "pve-container\@$vmid"];
+
+ PVE::GuestHelpers::exec_hookscript($conf, $vmid, 'pre-start', 1);
+ eval { PVE::Tools::run_command($cmd); };
+ if (my $err = $@) {
+ unlink $skiplock_flag_fn;
+ die $err;
+ }
+ PVE::GuestHelpers::exec_hookscript($conf, $vmid, 'post-start');
+
+ return;
+}
+
+# Helper to stop a container completely and make sure it has stopped completely.
+# This is necessary because we want the post-stop hook to have completed its
+# unmount-all step, but post-stop happens after lxc puts the container into the
+# STOPPED state.
+# $kill - if true it will always do an immediate hard-stop
+# $shutdown_timeout - the timeout to wait for a gracefull shutdown
+# $kill_after_timeout - if true, send a hardstop if shutdown timed out
+sub vm_stop {
+ my ($vmid, $kill, $shutdown_timeout, $kill_after_timeout) = @_;
+
+ # Open the container's command socket.
+ my $path = "\0/var/lib/lxc/$vmid/command";
+ my $sock = IO::Socket::UNIX->new(
+ Type => SOCK_STREAM(),
+ Peer => $path,
+ );
+ if (!$sock) {
+ return if $! == ECONNREFUSED; # The container is not running
+ die "failed to open container ${vmid}'s command socket: $!\n";
+ }
+
+ my $conf = PVE::LXC::Config->load_config($vmid);
+ PVE::GuestHelpers::exec_hookscript($conf, $vmid, 'pre-stop');
+
+ # Stop the container:
+
+ my $cmd = ['lxc-stop', '-n', $vmid];
+
+ if ($kill) {
+ push @$cmd, '--kill'; # doesn't allow timeouts
+ } else {
+ # lxc-stop uses a default timeout
+ push @$cmd, '--nokill' if !$kill_after_timeout;
+
+ if (defined($shutdown_timeout)) {
+ push @$cmd, '--timeout', $shutdown_timeout;
+ # Give run_command 5 extra seconds
+ $shutdown_timeout += 5;
+ }
+ }
+
+ eval { PVE::Tools::run_command($cmd, timeout => $shutdown_timeout) };
+ if (my $err = $@) {
+ warn $@ if $@;
+ }
+
+ my $result = <$sock>;
+
+ return if !defined $result; # monitor is gone and the ct has stopped.
+ die "container did not stop\n";
+}
+
+sub vm_reboot {
+ my ($vmid, $timeout, $skiplock) = @_;
+
+ PVE::LXC::Config->lock_config($vmid, sub {
+ return if !check_running($vmid);
+
+ vm_stop($vmid, 0, $timeout, 1); # kill if timeout exceeds
+
+ my $conf = PVE::LXC::Config->load_config($vmid);
+ vm_start($vmid, $conf);
+ });
+}
+
+sub run_unshared {
+ my ($code) = @_;
+
+ return PVE::Tools::run_fork(sub {
+ # Unshare the mount namespace
+ die "failed to unshare mount namespace: $!\n"
+ if !PVE::Tools::unshare(PVE::Tools::CLONE_NEWNS);
+ PVE::Tools::run_command(['mount', '--make-rslave', '/']);
+ return $code->();
+ });
+}
+
+my $copy_volume = sub {
+ my ($src_volid, $src, $dst_volid, $dest, $storage_cfg, $snapname, $bwlimit, $rootuid, $rootgid) = @_;
+
+ my $src_mp = { volume => $src_volid, mp => '/', ro => 1 };
+ $src_mp->{type} = PVE::LXC::Config->classify_mountpoint($src_volid);
+
+ my $dst_mp = { volume => $dst_volid, mp => '/', ro => 0 };
+ $dst_mp->{type} = PVE::LXC::Config->classify_mountpoint($dst_volid);
+
+ my @mounted;
+ eval {
+ # mount and copy
+ mkdir $src;
+ mountpoint_mount($src_mp, $src, $storage_cfg, $snapname, $rootuid, $rootgid);
+ push @mounted, $src;
+ mkdir $dest;
+ mountpoint_mount($dst_mp, $dest, $storage_cfg, undef, $rootuid, $rootgid);
+ push @mounted, $dest;
+
+ $bwlimit //= 0;
+
+ PVE::Tools::run_command(['/usr/bin/rsync', '--stats', '-X', '-A', '--numeric-ids',
+ '-aH', '--whole-file', '--sparse', '--one-file-system',
+ "--bwlimit=$bwlimit", "$src/", $dest]);
+ };
+ my $err = $@;
+
+ # Wait for rsync's children to release dest so that
+ # consequent file operations (umount, remove) are possible
+ while ((system {"fuser"} "fuser", "-s", $dest) == 0) {sleep 1};
+
+ foreach my $mount (reverse @mounted) {
+ eval { PVE::Tools::run_command(['/bin/umount', $mount], errfunc => sub{})};
+ warn "Can't umount $mount\n" if $@;
+ }
+
+ # If this fails they're used as mount points in a concurrent operation
+ # (which should not happen but there's also no real need to get rid of them).
+ rmdir $dest;
+ rmdir $src;
+
+ die $err if $err;
+};
+
+# Should not be called after unsharing the mount namespace!
+sub copy_volume {
+ my ($mp, $vmid, $storage, $storage_cfg, $conf, $snapname, $bwlimit) = @_;
+
+ die "cannot copy volumes of type $mp->{type}\n" if $mp->{type} ne 'volume';
+ File::Path::make_path("/var/lib/lxc/$vmid");
+ my $dest = "/var/lib/lxc/$vmid/.copy-volume-1";
+ my $src = "/var/lib/lxc/$vmid/.copy-volume-2";
+
+ # get id's for unprivileged container
+ my (undef, $rootuid, $rootgid) = parse_id_maps($conf);
+
+ # Allocate the disk before unsharing in order to make sure zfs subvolumes
+ # are visible in this namespace, otherwise the host only sees the empty
+ # (not-mounted) directory.
+ my $new_volid;
+ eval {
+ # Make sure $mp contains a correct size.
+ $mp->{size} = PVE::Storage::volume_size_info($storage_cfg, $mp->{volume});
+ my $needs_chown;
+ ($new_volid, $needs_chown) = alloc_disk($storage_cfg, $vmid, $storage, $mp->{size}/1024, $rootuid, $rootgid);
+ if ($needs_chown) {
+ PVE::Storage::activate_volumes($storage_cfg, [$new_volid], undef);
+ my $path = PVE::Storage::path($storage_cfg, $new_volid, undef);
+ chown($rootuid, $rootgid, $path);
+ }
+
+ run_unshared(sub {
+ $copy_volume->($mp->{volume}, $src, $new_volid, $dest, $storage_cfg, $snapname, $bwlimit, $rootuid, $rootgid);
+ });
+ };
+ if (my $err = $@) {
+ PVE::Storage::vdisk_free($storage_cfg, $new_volid)
+ if defined($new_volid);
+ die $err;
+ }
+
+ return $new_volid;
+}