]> git.proxmox.com Git - pve-container.git/blobdiff - src/PVE/LXC.pm
fix #5414: use proper percentages in `pct df`
[pve-container.git] / src / PVE / LXC.pm
index 847b8c8e9c376fe37c06730fbb82022ebcc7635c..65d0fa8150ec793927115293749a42366d81db7d 100644 (file)
@@ -5,7 +5,7 @@ use warnings;
 
 use Cwd qw();
 use Errno qw(ELOOP ENOTDIR EROFS ECONNREFUSED EEXIST);
-use Fcntl qw(O_RDONLY O_WRONLY O_NOFOLLOW O_DIRECTORY);
+use Fcntl qw(O_RDONLY O_WRONLY O_NOFOLLOW O_DIRECTORY :mode);
 use File::Path;
 use File::Spec;
 use IO::Poll qw(POLLIN POLLHUP);
@@ -18,6 +18,7 @@ use PVE::AccessControl;
 use PVE::CGroup;
 use PVE::CpuSet;
 use PVE::Exception qw(raise_perm_exc);
+use PVE::Firewall;
 use PVE::GuestHelpers qw(check_vnet_access safe_string_ne safe_num_ne safe_boolean_ne);
 use PVE::INotify;
 use PVE::JSONSchema qw(get_standard_option);
@@ -69,7 +70,7 @@ sub config_list {
        my $d = $ids->{$vmid};
        next if !$d->{node} || $d->{node} ne $nodename;
        next if !$d->{type} || $d->{type} ne 'lxc';
-       $res->{$vmid} = { type => 'lxc', vmid => $vmid };
+       $res->{$vmid} = { type => 'lxc', vmid => int($vmid) };
     }
     return $res;
 }
@@ -640,6 +641,19 @@ sub update_lxc_config {
        $raw .= "lxc.mount.auto = sys:mixed\n";
     }
 
+    PVE::LXC::Config->foreach_passthrough_device($conf, sub {
+       my ($key, $device) = @_;
+
+       die "Path is not defined for passthrough device $key\n"
+           if !defined($device->{path});
+
+       my ($mode, $rdev) = PVE::LXC::Tools::get_device_mode_and_rdev($device->{path});
+       my $major = PVE::Tools::dev_t_major($rdev);
+       my $minor = PVE::Tools::dev_t_minor($rdev);
+       my $device_type_char = S_ISBLK($mode) ? 'b' : 'c';
+       $raw .= "lxc.cgroup2.devices.allow = $device_type_char $major:$minor rw\n";
+    });
+
     # WARNING: DO NOT REMOVE this without making sure that loop device nodes
     # cannot be exposed to the container with r/w access (cgroup perms).
     # When this is enabled mounts will still remain in the monitor's namespace
@@ -933,14 +947,17 @@ sub net_tap_plug : prototype($$) {
        return;
     }
 
-    my ($bridge, $tag, $firewall, $trunks, $rate, $hwaddr) =
-       $net->@{'bridge', 'tag', 'firewall', 'trunks', 'rate', 'hwaddr'};
+    my ($bridge, $tag, $trunks, $rate, $hwaddr) =
+       $net->@{'bridge', 'tag', 'trunks', 'rate', 'hwaddr'};
+
+    # The nftable-based implementation from the newer proxmox-firewall does not requires FW bridges
+    my $create_firewall_bridges = $net->{firewall} && !PVE::Firewall::is_nftables();
 
     if ($have_sdn) {
-       PVE::Network::SDN::Zones::tap_plug($iface, $bridge, $tag, $firewall, $trunks, $rate);
+       PVE::Network::SDN::Zones::tap_plug($iface, $bridge, $tag, $create_firewall_bridges, $trunks, $rate);
        PVE::Network::SDN::Zones::add_bridge_fdb($iface, $hwaddr, $bridge);
     } else {
-       PVE::Network::tap_plug($iface, $bridge, $tag, $firewall, $trunks, $rate, { mac => $hwaddr });
+       PVE::Network::tap_plug($iface, $bridge, $tag, $create_firewall_bridges, $trunks, $rate, { mac => $hwaddr });
     }
 
     PVE::Tools::run_command(['/sbin/ip', 'link', 'set', 'dev', $iface, 'up']);
@@ -970,7 +987,7 @@ sub update_net {
                warn $@ if $@;
 
                PVE::Network::SDN::Vnets::add_next_free_cidr($newnet->{bridge}, $conf->{hostname}, $newnet->{hwaddr}, $vmid, undef, 1);
-               PVE::Network::SDN::Vnets::add_dhcp_mapping($newnet->{bridge}, $newnet->{hwaddr});
+               PVE::Network::SDN::Vnets::add_dhcp_mapping($newnet->{bridge}, $newnet->{hwaddr}, $vmid, $conf->{hostname});
            }
 
            delete $conf->{$opt};
@@ -1021,8 +1038,10 @@ sub update_net {
            PVE::LXC::Config->write_config($vmid, $conf);
        }
     } else {
-       PVE::Network::SDN::Vnets::add_next_free_cidr($newnet->{bridge}, $conf->{hostname}, $newnet->{hwaddr}, $vmid, undef, 1);
-       PVE::Network::SDN::Vnets::add_dhcp_mapping($newnet->{bridge}, $newnet->{hwaddr});
+       if ($have_sdn) {
+           PVE::Network::SDN::Vnets::add_next_free_cidr($newnet->{bridge}, $conf->{hostname}, $newnet->{hwaddr}, $vmid, undef, 1);
+           PVE::Network::SDN::Vnets::add_dhcp_mapping($newnet->{bridge}, $newnet->{hwaddr}, $vmid, $conf->{hostname});
+       }
 
        hotplug_net($vmid, $conf, $opt, $newnet, $netid);
     }
@@ -1370,6 +1389,8 @@ sub check_ct_modify_config_perm {
            $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
            check_bridge_access($rpcenv, $authuser, $oldconf->{$opt}) if $oldconf->{$opt};
            check_bridge_access($rpcenv, $authuser, $newconf->{$opt}) if $newconf->{$opt};
+       } elsif ($opt =~ m/^dev\d+$/) {
+           raise_perm_exc("configuring device passthrough is only allowed for root\@pam");
        } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' || $opt eq 'hostname') {
            $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
        } elsif ($opt eq 'features') {
@@ -1496,7 +1517,7 @@ sub mount_all {
     my $volid_list = PVE::LXC::Config->get_vm_volumes($conf);
     PVE::Storage::activate_volumes($storage_cfg, $volid_list);
 
-    my (undef, $rootuid, $rootgid) = parse_id_maps($conf);
+    my (undef, $root_uid, $root_gid) = parse_id_maps($conf);
 
     eval {
        PVE::LXC::Config->foreach_volume($conf, sub {
@@ -1504,7 +1525,7 @@ sub mount_all {
 
            $mountpoint->{ro} = 0 if $ignore_ro;
 
-           mountpoint_mount($mountpoint, $rootdir, $storage_cfg, undef, $rootuid, $rootgid);
+           mountpoint_mount($mountpoint, $rootdir, $storage_cfg, undef, $root_uid, $root_gid);
         });
     };
     if (my $err = $@) {
@@ -1580,17 +1601,17 @@ sub run_with_loopdev {
 #   * directory name of the last directory
 # So that the path $2/$3 should lead to $1 afterwards.
 sub walk_tree_nofollow($$$;$$) {
-    my ($start, $subdir, $mkdir, $rootuid, $rootgid) = @_;
+    my ($start, $subdir, $mkdir, $root_uid, $root_gid) = @_;
 
     sysopen(my $fd, $start, O_PATH | O_DIRECTORY)
        or die "failed to open start directory $start: $!\n";
 
-    return walk_tree_nofollow_fd($start, $fd, $subdir, $mkdir, $rootuid, $rootgid);
+    return walk_tree_nofollow_fd($start, $fd, $subdir, $mkdir, $root_uid, $root_gid);
 }
 
 
 sub walk_tree_nofollow_fd($$$$;$$) {
-    my ($start_dirname, $start_fd, $subdir, $mkdir, $rootuid, $rootgid) = @_;
+    my ($start_dirname, $start_fd, $subdir, $mkdir, $root_uid, $root_gid) = @_;
 
     # splitdir() returns '' for empty components including the leading /
     my @comps = grep { length($_)>0 } File::Spec->splitdir($subdir);
@@ -1618,8 +1639,8 @@ sub walk_tree_nofollow_fd($$$$;$$) {
            $next = PVE::Tools::openat(fileno($fd), $component, O_NOFOLLOW | O_DIRECTORY);
            die "failed to create path: $dir: $!\n" if !$next;
 
-           PVE::Tools::fchownat(fileno($next), '', $rootuid, $rootgid, PVE::Tools::AT_EMPTY_PATH)
-               if defined($rootuid) && defined($rootgid);
+           PVE::Tools::fchownat(fileno($next), '', $root_uid, $root_gid, PVE::Tools::AT_EMPTY_PATH)
+               if defined($root_uid) && defined($root_gid);
        }
 
        close $second if defined($last_component) && $second != $start_fd;
@@ -1713,24 +1734,24 @@ sub bindmount {
 # from $rootdir and $mount and walk the path from $rootdir to the final
 # directory to check for symlinks.
 sub __mount_prepare_rootdir {
-    my ($rootdir, $mount, $rootuid, $rootgid) = @_;
+    my ($rootdir, $mount, $root_uid, $root_gid) = @_;
     $rootdir =~ s!/+!/!g;
     $rootdir =~ s!/+$!!;
     my $mount_path = "$rootdir/$mount";
-    my ($mpfd, $parentfd, $last_dir) = walk_tree_nofollow($rootdir, $mount, 1, $rootuid, $rootgid);
+    my ($mpfd, $parentfd, $last_dir) = walk_tree_nofollow($rootdir, $mount, 1, $root_uid, $root_gid);
     return ($rootdir, $mount_path, $mpfd, $parentfd, $last_dir);
 }
 
 # use $rootdir = undef to just return the corresponding mount path
 sub mountpoint_mount {
-    my ($mountpoint, $rootdir, $storage_cfg, $snapname, $rootuid, $rootgid) = @_;
-    return __mountpoint_mount($mountpoint, $rootdir, $storage_cfg, $snapname, $rootuid, $rootgid, undef);
+    my ($mountpoint, $rootdir, $storage_cfg, $snapname, $root_uid, $root_gid) = @_;
+    return __mountpoint_mount($mountpoint, $rootdir, $storage_cfg, $snapname, $root_uid, $root_gid, undef);
 }
 
 sub mountpoint_stage {
-    my ($mountpoint, $stage_dir, $storage_cfg, $snapname, $rootuid, $rootgid) = @_;
+    my ($mountpoint, $stage_dir, $storage_cfg, $snapname, $root_uid, $root_gid) = @_;
     my ($path, $loop, $dev) =
-       __mountpoint_mount($mountpoint, $stage_dir, $storage_cfg, $snapname, $rootuid, $rootgid, 1);
+       __mountpoint_mount($mountpoint, $stage_dir, $storage_cfg, $snapname, $root_uid, $root_gid, 1);
 
     if (!defined($path)) {
        die "failed to mount subvolume: $!\n";
@@ -1747,14 +1768,14 @@ sub mountpoint_stage {
 }
 
 sub mountpoint_insert_staged {
-    my ($mount_fd, $rootdir_fd, $mp_dir, $opt, $rootuid, $rootgid) = @_;
+    my ($mount_fd, $rootdir_fd, $mp_dir, $opt, $root_uid, $root_gid) = @_;
 
     if (!defined($rootdir_fd)) {
        sysopen($rootdir_fd, '.', O_PATH | O_DIRECTORY)
            or die "failed to open '.': $!\n";
     }
 
-    my $dest_fd = walk_tree_nofollow_fd('/', $rootdir_fd, $mp_dir, 1, $rootuid, $rootgid);
+    my $dest_fd = walk_tree_nofollow_fd('/', $rootdir_fd, $mp_dir, 1, $root_uid, $root_gid);
 
     PVE::Tools::move_mount(
        fileno($mount_fd),
@@ -1768,7 +1789,7 @@ sub mountpoint_insert_staged {
 # Use $stage_mount, $rootdir is treated as a temporary path to "stage" the file system. The user
 #   can then open a file descriptor to it which can be used with the `move_mount` syscall.
 sub __mountpoint_mount {
-    my ($mountpoint, $rootdir, $storage_cfg, $snapname, $rootuid, $rootgid, $stage_mount) = @_;
+    my ($mountpoint, $rootdir, $storage_cfg, $snapname, $root_uid, $root_gid, $stage_mount) = @_;
 
     # When staging mount points we always mount to $rootdir directly (iow. as if `mp=/`).
     # This is required since __mount_prepare_rootdir() will return handles to the parent directory
@@ -1789,7 +1810,7 @@ sub __mountpoint_mount {
 
     if (defined($rootdir)) {
        ($rootdir, $mount_path, $mpfd, $parentfd, $last_dir) =
-           __mount_prepare_rootdir($rootdir, $mount, $rootuid, $rootgid);
+           __mount_prepare_rootdir($rootdir, $mount, $root_uid, $root_gid);
     }
 
     if (defined($stage_mount)) {
@@ -1808,8 +1829,10 @@ sub __mountpoint_mount {
     }
 
     my $acl = $mountpoint->{acl};
-    if (defined($acl)) {
-       push @$optlist, ($acl ? 'acl' : 'noacl');
+
+    if ($acl) {
+       push @$optlist, 'acl';
+       # NOTE: the else branch is handled below
     }
 
     my $optstring = join(',', @$optlist);
@@ -1822,6 +1845,7 @@ sub __mountpoint_mount {
 
        my $scfg = PVE::Storage::storage_config($storage_cfg, $storage);
 
+       PVE::Storage::activate_volumes($storage_cfg, [$volid], $snapname);
        my $path = PVE::Storage::map_volume($storage_cfg, $volid, $snapname);
 
        $path = PVE::Storage::path($storage_cfg, $volid, $snapname) if !defined($path);
@@ -1829,6 +1853,12 @@ sub __mountpoint_mount {
        my ($vtype, undef, undef, undef, undef, $isBase, $format) =
            PVE::Storage::parse_volname($storage_cfg, $volid);
 
+       if (defined($acl) && !$acl) {
+           # Does having this really makes sense or should we drop it with a future major release?
+           # Kernel 6.1 removed the noacl mount option for ext4, which is used for all raw volumes.
+           push @$optlist, 'noacl' if $format ne 'raw';
+       }
+
        $format = 'iso' if $vtype eq 'iso'; # allow to handle iso files
 
        if ($format eq 'subvol') {
@@ -1911,7 +1941,7 @@ sub __mountpoint_mount {
 sub mountpoint_hotplug :prototype($$$$$) {
     my ($vmid, $conf, $opt, $mp, $storage_cfg) = @_;
 
-    my (undef, $rootuid, $rootgid) = PVE::LXC::parse_id_maps($conf);
+    my (undef, $root_uid, $root_gid) = PVE::LXC::parse_id_maps($conf);
 
     # We do the rest in a fork with an unshared mount namespace, because:
     #  -) change our papparmor profile to that of /usr/bin/lxc-start
@@ -1947,21 +1977,22 @@ sub mountpoint_hotplug :prototype($$$$$) {
        my $dir = get_staging_mount_path($opt);
 
        # Now switch our apparmor profile before mounting:
-       my $data = 'changeprofile /usr/bin/lxc-start';
-       if (syswrite($aa_fd, $data, length($data)) != length($data)) {
+       my $data = 'changeprofile pve-container-mounthotplug';
+       my $data_written = syswrite($aa_fd, $data, length($data));
+       if (!defined($data_written) || $data_written != length($data)) {
            die "failed to change apparmor profile: $!\n";
        }
        # Check errors on close as well:
        close($aa_fd)
            or die "failed to change apparmor profile (close() failed): $!\n";
 
-       my $mount_fd = mountpoint_stage($mp, $dir, $storage_cfg, undef, $rootuid, $rootgid);
+       my $mount_fd = mountpoint_stage($mp, $dir, $storage_cfg, undef, $root_uid, $root_gid);
 
        PVE::Tools::setns(fileno($ct_mnt_ns), PVE::Tools::CLONE_NEWNS);
        chdir('/')
            or die "failed to change root directory within the container's mount namespace: $!\n";
 
-       mountpoint_insert_staged($mount_fd, undef, $mp->{mp}, $opt, $rootuid, $rootgid);
+       mountpoint_insert_staged($mount_fd, undef, $mp->{mp}, $opt, $root_uid, $root_gid);
     });
 }
 
@@ -1994,7 +2025,7 @@ sub get_staging_tempfs() {
 }
 
 sub mkfs {
-    my ($dev, $rootuid, $rootgid) = @_;
+    my ($dev, $root_uid, $root_gid) = @_;
 
     run_command(
        [
@@ -2002,7 +2033,7 @@ sub mkfs {
            '-O',
            'mmp',
            '-E',
-           "root_owner=$rootuid:$rootgid",
+           "root_owner=$root_uid:$root_gid",
            $dev,
        ],
        outfunc => sub {
@@ -2020,10 +2051,12 @@ sub mkfs {
 }
 
 sub format_disk {
-    my ($storage_cfg, $volid, $rootuid, $rootgid) = @_;
+    my ($storage_cfg, $volid, $root_uid, $root_gid) = @_;
 
     if ($volid =~ m!^/dev/.+!) {
-       mkfs($volid);
+       # FIXME: remove in Proxmox VE 9 – this code path cannot really be reached currently, using
+       # block devices needs manual preparations by the user
+       mkfs($volid, $root_uid, $root_gid);
        return;
     }
 
@@ -2043,7 +2076,7 @@ sub format_disk {
     die "cannot format volume '$volid' (format == $format)\n"
        if $format ne 'raw';
 
-    mkfs($path, $rootuid, $rootgid);
+    mkfs($path, $root_uid, $root_gid);
 }
 
 sub destroy_disks {
@@ -2056,7 +2089,7 @@ sub destroy_disks {
 }
 
 sub alloc_disk {
-    my ($storecfg, $vmid, $storage, $size_kb, $rootuid, $rootgid) = @_;
+    my ($storecfg, $vmid, $storage, $size_kb, $root_uid, $root_gid) = @_;
 
     my $needs_chown = 0;
     my $volid;
@@ -2083,7 +2116,7 @@ sub alloc_disk {
        } else {
            die "content type 'rootdir' is not available or configured on storage '$storage'\n";
        }
-       format_disk($storecfg, $volid, $rootuid, $rootgid) if $do_format;
+       format_disk($storecfg, $volid, $root_uid, $root_gid) if $do_format;
     };
     if (my $err = $@) {
        # in case formatting got interrupted:
@@ -2103,7 +2136,7 @@ sub create_disks {
     my $vollist = [];
 
     eval {
-       my (undef, $rootuid, $rootgid) = PVE::LXC::parse_id_maps($conf);
+       my (undef, $root_uid, $root_gid) = PVE::LXC::parse_id_maps($conf);
        my $chown_vollist = [];
 
        PVE::LXC::Config->foreach_volume($settings, sub {
@@ -2120,7 +2153,7 @@ sub create_disks {
                my $size_kb = int(${size_gb}*1024) * 1024;
 
                my $needs_chown = 0;
-               ($volid, $needs_chown) = alloc_disk($storecfg, $vmid, $storage, $size_kb, $rootuid, $rootgid);
+               ($volid, $needs_chown) = alloc_disk($storecfg, $vmid, $storage, $size_kb, $root_uid, $root_gid);
                push @$chown_vollist, $volid if $needs_chown;
                push @$vollist, $volid;
                $mountpoint->{volume} = $volid;
@@ -2139,7 +2172,7 @@ sub create_disks {
        PVE::Storage::activate_volumes($storecfg, $chown_vollist, undef);
        foreach my $volid (@$chown_vollist) {
            my $path = PVE::Storage::path($storecfg, $volid, undef);
-           chown($rootuid, $rootgid, $path);
+           chown($root_uid, $root_gid, $path);
        }
        PVE::Storage::deactivate_volumes($storecfg, $chown_vollist, undef);
     };
@@ -2353,8 +2386,8 @@ sub parse_id_maps {
     my ($conf) = @_;
 
     my $id_map = [];
-    my $rootuid = 0;
-    my $rootgid = 0;
+    my $root_uid = 0;
+    my $root_gid = 0;
 
     my $lxc = $conf->{lxc};
     foreach my $entry (@$lxc) {
@@ -2366,8 +2399,8 @@ sub parse_id_maps {
            my ($type, $ct, $host, $length) = ($1, $2, $3, $4);
            push @$id_map, [$type, $ct, $host, $length];
            if ($ct == 0) {
-               $rootuid = $host if $type eq 'u';
-               $rootgid = $host if $type eq 'g';
+               $root_uid = $host if $type eq 'u';
+               $root_gid = $host if $type eq 'g';
            }
        } else {
            die "failed to parse idmap: $value\n";
@@ -2378,10 +2411,10 @@ sub parse_id_maps {
        # Should we read them from /etc/subuid?
        $id_map = [ ['u', '0', '100000', '65536'],
                    ['g', '0', '100000', '65536'] ];
-       $rootuid = $rootgid = 100000;
+       $root_uid = $root_gid = 100000;
     }
 
-    return ($id_map, $rootuid, $rootgid);
+    return ($id_map, $root_uid, $root_gid);
 }
 
 sub validate_id_maps {
@@ -2419,6 +2452,34 @@ sub validate_id_maps {
     }
 }
 
+sub map_ct_id_to_host {
+    my ($id, $id_map, $id_type) = @_;
+
+    for my $mapping (@$id_map) {
+       my ($type, $ct, $host, $length) = @$mapping;
+
+       next if ($type ne $id_type);
+
+       if ($id >= $ct && $id < ($ct + $length)) {
+           return $host - $ct + $id;
+       }
+    }
+
+    return $id;
+}
+
+sub map_ct_uid_to_host {
+    my ($uid, $id_map) = @_;
+
+    return map_ct_id_to_host($uid, $id_map, 'u');
+}
+
+sub map_ct_gid_to_host {
+    my ($gid, $id_map) = @_;
+
+    return map_ct_id_to_host($gid, $id_map, 'g');
+}
+
 sub userns_command {
     my ($id_map) = @_;
     if (@$id_map) {
@@ -2636,7 +2697,7 @@ sub run_unshared {
 }
 
 my $copy_volume = sub {
-    my ($src_volid, $src, $dst_volid, $dest, $storage_cfg, $snapname, $bwlimit, $rootuid,  $rootgid) = @_;
+    my ($src_volid, $src, $dst_volid, $dest, $storage_cfg, $snapname, $bwlimit, $root_uid,  $root_gid) = @_;
 
     my $src_mp = { volume => $src_volid, mp => '/', ro => 1 };
     $src_mp->{type} = PVE::LXC::Config->classify_mountpoint($src_volid);
@@ -2648,10 +2709,10 @@ my $copy_volume = sub {
     eval {
        # mount and copy
        mkdir $src;
-       mountpoint_mount($src_mp, $src, $storage_cfg, $snapname, $rootuid, $rootgid);
+       mountpoint_mount($src_mp, $src, $storage_cfg, $snapname, $root_uid, $root_gid);
        push @mounted, $src;
        mkdir $dest;
-       mountpoint_mount($dst_mp, $dest, $storage_cfg, undef, $rootuid, $rootgid);
+       mountpoint_mount($dst_mp, $dest, $storage_cfg, undef, $root_uid, $root_gid);
        push @mounted, $dest;
 
        $bwlimit //= 0;
@@ -2700,7 +2761,7 @@ sub copy_volume {
     my $src  = "/var/lib/lxc/$vmid/.copy-volume-2";
 
     # get id's for unprivileged container
-    my (undef, $rootuid, $rootgid) = parse_id_maps($conf);
+    my (undef, $root_uid, $root_gid) = 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
@@ -2710,15 +2771,15 @@ sub copy_volume {
        # 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);
+       ($new_volid, $needs_chown) = alloc_disk($storage_cfg, $vmid, $storage, $mp->{size}/1024, $root_uid, $root_gid);
        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);
+           chown($root_uid, $root_gid, $path);
        }
 
        run_unshared(sub {
-           $copy_volume->($mp->{volume}, $src, $new_volid, $dest, $storage_cfg, $snapname, $bwlimit, $rootuid, $rootgid);
+           $copy_volume->($mp->{volume}, $src, $new_volid, $dest, $storage_cfg, $snapname, $bwlimit, $root_uid, $root_gid);
        });
     };
     if (my $err = $@) {