[pve-devel] [PATCH common 5/5] Tools: add new mount api wrappers
Signed-off-by: Wolfgang Bumiller --- src/PVE/Tools.pm | 65 1 file changed, 65 insertions(+) diff --git a/src/PVE/Tools.pm b/src/PVE/Tools.pm index 801977d..02c2886 100644 --- a/src/PVE/Tools.pm +++ b/src/PVE/Tools.pm @@ -1696,5 +1696,70 @@ sub array_intersect { return $return_arr; } +sub open_tree($$$) { +my ($dfd, $pathname, $flags) = @_; +return PVE::Syscall::file_handle_result(syscall( + &PVE::Syscall::open_tree, + $dfd, + $pathname, + $flags, +)); +} + +sub move_mount($) { +my ($from_dirfd, $from_pathname, $to_dirfd, $to_pathname, $flags) = @_; +return 0 == syscall( + &PVE::Syscall::move_mount, + $from_dirfd, + $from_pathname, + $to_dirfd, + $to_pathname, + $flags, +); +} + +sub fsopen($$) { +my ($fsname, $flags) = @_; +return PVE::Syscall::file_handle_result(syscall(&PVE::Syscall::fsopen, $fsname, $flags)); +} + +sub fsmount($$$) { +my ($fd, $flags, $mount_attrs) = @_; +return PVE::Syscall::file_handle_result(syscall( + &PVE::Syscall::fsmount, + $fd, + $flags, + $mount_attrs, +)); +} + +sub fspick($$$) { +my ($dirfd, $pathname, $flags) = @_; +return PVE::Syscall::file_handle_result(syscall( + &PVE::Syscall::fspick, + $dirfd, + $pathname, + $flags, +)); +} + +sub fsconfig($) { +my ($fd, $command, $key, $value, $aux) = @_; +return 0 == syscall(&PVE::Syscall::fsconfig, $fd, $command, $key, $value, $aux); +} + +# "raw" mount, old api, not for generic use (as it does not invoke any helpers). +# use for lower level stuff such as bind/remount/... or simple tmpfs mounts +sub mount($) { +my ($source, $target, $filesystemtype, $mountflags, $data) = @_; +return 0 == syscall( + &PVE::Syscall::mount, + $source, + $target, + $filesystemtype, + $mountflags, + $data, +); +} 1; -- 2.20.1 ___ pve-devel mailing list pve-devel@pve.proxmox.com https://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
[pve-devel] [PATCH container 5/8] add mount stage directory helpers
Signed-off-by: Wolfgang Bumiller --- src/PVE/LXC.pm | 29 - 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/PVE/LXC.pm b/src/PVE/LXC.pm index 1225c8b..eb4313d 100644 --- a/src/PVE/LXC.pm +++ b/src/PVE/LXC.pm @@ -11,7 +11,7 @@ use File::Path; use File::Spec; use Cwd qw(); use Fcntl qw(O_RDONLY O_NOFOLLOW O_DIRECTORY); -use Errno qw(ELOOP ENOTDIR EROFS ECONNREFUSED ENOSYS); +use Errno qw(ELOOP ENOTDIR EROFS ECONNREFUSED ENOSYS EEXIST); use IO::Socket::UNIX; use PVE::Exception qw(raise_perm_exc); @@ -1628,6 +1628,33 @@ sub __mountpoint_mount { die "unsupported storage"; } +# Create a directory in the mountpoint staging tempfs. +sub get_staging_mount_path($) { +my ($opt) = @_; + +my $target = get_staging_tempfs() . "/$opt"; +if (!mkdir($target) && $! != EEXIST) { + die "failed to create directory $target: $!\n"; +} + +return $target; +} + +# Mount /run/pve/mountpoints as tmpfs +sub get_staging_tempfs() { +my $target = '/run/pve/mountpoints'; +mkdir("/run/pve"); +if (!mkdir($target)) { + return $target if $! == EEXIST; + die "failed to create directory $target: $!\n"; +} + +PVE::Tools::mount("none", $target, 'tmpfs', 0, "size=8k,mode=755") + or die "failed to mount $target as tmpfs: $!\n"; + +return $target; +} + sub mkfs { my ($dev, $rootuid, $rootgid) = @_; -- 2.20.1 ___ pve-devel mailing list pve-devel@pve.proxmox.com https://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
[pve-devel] [PATCH container 2/8] add open_pid_fd, open_lxc_pid, open_ppid helpers
Getting a pid and acting on it is always a race, so add safer helpers for this. Signed-off-by: Wolfgang Bumiller --- src/PVE/LXC.pm | 38 ++ 1 file changed, 38 insertions(+) diff --git a/src/PVE/LXC.pm b/src/PVE/LXC.pm index b5a97b8..3bbaa36 100644 --- a/src/PVE/LXC.pm +++ b/src/PVE/LXC.pm @@ -387,6 +387,44 @@ sub find_lxc_pid { return $pid; } +sub open_pid_fd($) { +my ($pid) = @_; +sysopen(my $fd, "/proc/$pid", O_RDONLY | O_DIRECTORY) + or die "failed to open /proc/$pid pid fd\n"; +return $fd; +} + +sub open_lxc_pid { +my ($vmid) = @_; + +# Find the pid and open: +my $pid = find_lxc_pid($vmid); +my $fd = open_pid_fd($pid); + +# Verify: +my $pid2 = find_lxc_pid($vmid); + +return () if $pid != $pid2; +return ($pid, $fd); +} + +sub open_ppid { +my ($pid) = @_; + +# Find the parent pid via proc and open it: +my $stat = PVE::ProcFSTools::read_proc_pid_stat($pid); +my $ppid = $stat->{ppid} // die "failed to get parent pid\n"; + +my $fd = open_pid_fd($ppid); + +# Verify: +$stat = PVE::ProcFSTools::read_proc_pid_stat($pid); +my $ppid2 = $stat->{ppid} // die "failed to get parent pid for verification\n"; + +return () if $ppid != $ppid2; +return ($ppid, $fd); +} + # Note: we cannot use Net:IP, because that only allows strict # CIDR networks sub parse_ipv4_cidr { -- 2.20.1 ___ pve-devel mailing list pve-devel@pve.proxmox.com https://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
[pve-devel] [PATCH container 4/8] add get_container_namespace helper
Signed-off-by: Wolfgang Bumiller --- src/PVE/LXC.pm | 13 + 1 file changed, 13 insertions(+) diff --git a/src/PVE/LXC.pm b/src/PVE/LXC.pm index d40f490..1225c8b 100644 --- a/src/PVE/LXC.pm +++ b/src/PVE/LXC.pm @@ -1047,6 +1047,19 @@ my $enter_namespace = sub { close $fd; }; +my $get_container_namespace = sub { +my ($vmid, $pid, $kind) = @_; + +my $pidfd; +if (!defined($pid)) { + # Pin the pid while we're grabbing its stuff from /proc + ($pid, $pidfd) = open_lxc_pid($vmid) + or die "failed to open pidfd of container $vmid\'s init process\n"; +} + +return $open_namespace->($vmid, $pid, $kind); +}; + my $do_syncfs = sub { my ($vmid, $pid, $socket) = @_; -- 2.20.1 ___ pve-devel mailing list pve-devel@pve.proxmox.com https://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
[pve-devel] [PATCH container 1/8] implement "staged mountpoints"
Staging a mount point requires the new kernel mount API and will mount the volume at a fixed path, then use open_tree() to "pick it up" into a file descriptor. For most of our volumes we wouldn't need the temp directory, but some things cannot be handled with _only_ the new API (like single-step read-only bind mounts). Additionally, the 'mount' command figures out file systems automatically and has a bunch of helpers we'd need to reimplement, so instead, go through our usual mount code and then pick up the result. This can then be used to implement mount point hotplugging, as with the open file descriptor we can move into the container's namespace and issue a `move_mount()` to put the mount point in place in the running container. Signed-off-by: Wolfgang Bumiller --- src/PVE/LXC.pm | 47 +++ 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/src/PVE/LXC.pm b/src/PVE/LXC.pm index cdf6d64..b5a97b8 100644 --- a/src/PVE/LXC.pm +++ b/src/PVE/LXC.pm @@ -11,7 +11,7 @@ use File::Path; use File::Spec; use Cwd qw(); use Fcntl qw(O_RDONLY O_NOFOLLOW O_DIRECTORY); -use Errno qw(ELOOP ENOTDIR EROFS ECONNREFUSED); +use Errno qw(ELOOP ENOTDIR EROFS ECONNREFUSED ENOSYS); use IO::Socket::UNIX; use PVE::Exception qw(raise_perm_exc); @@ -19,12 +19,12 @@ use PVE::Storage; use PVE::SafeSyslog; use PVE::INotify; use PVE::JSONSchema qw(get_standard_option); -use PVE::Tools qw($IPV6RE $IPV4RE dir_glob_foreach lock_file lock_file_full O_PATH); +use PVE::Tools qw($IPV6RE $IPV4RE dir_glob_foreach lock_file lock_file_full O_PATH AT_FDCWD); use PVE::CpuSet; use PVE::Network; use PVE::AccessControl; use PVE::ProcFSTools; -use PVE::Syscall; +use PVE::Syscall qw(:fsmount); use PVE::LXC::Config; use PVE::GuestHelpers; @@ -1397,9 +1397,44 @@ sub __mount_prepare_rootdir { return ($rootdir, $mount_path, $mpfd, $parentfd, $last_dir); } -# use $rootdir = undef to just return the corresponding mount path +# 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); +} + +sub mountpoint_stage { +my ($mountpoint, $stage_dir, $storage_cfg, $snapname, $rootuid, $rootgid) = @_; +my ($path, $loop, $dev) = + __mountpoint_mount($mountpoint, $stage_dir, $storage_cfg, $snapname, $rootuid, $rootgid, 1); + +if (!defined($path)) { + return undef if $! == ENOSYS; + die "failed to mount subvolume: $!\n"; +} + +my $err; +my $fd = PVE::Tools::open_tree(&AT_FDCWD, $stage_dir, &OPEN_TREE_CLOEXEC | &OPEN_TREE_CLONE) + or die "open_tree() on mount point failed: $!\n"; + +return wantarray ? ($path, $loop, $dev, $fd) : $fd; +} + +# 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. +# Note that if the kernel does not support the new mount API, this will not perform any action +# and return `undef` with $! = ENOSYS. +sub __mountpoint_mount { +my ($mountpoint, $rootdir, $storage_cfg, $snapname, $rootuid, $rootgid, $stage_mount) = @_; + +if (defined($stage_mount)) { + # Test the kernel: + my $fd = PVE::Tools::fsopen("ext4", &FSOPEN_CLOEXEC) + or return undef; + # If we get here, the kernel is new enough to support our syscalls required for staging + # mount points. + close($fd); +} my $volid = $mountpoint->{volume}; my $mount = $mountpoint->{mp}; @@ -1418,6 +1453,10 @@ sub mountpoint_mount { ($rootdir, $mount_path, $mpfd, $parentfd, $last_dir) = __mount_prepare_rootdir($rootdir, $mount, $rootuid, $rootgid); } + +if (defined($stage_mount)) { + $mount_path = $rootdir; +} my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1); -- 2.20.1 ___ pve-devel mailing list pve-devel@pve.proxmox.com https://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
[pve-devel] [PATCH common 4/5] add missing 1; at the end of Syscall.pm
Signed-off-by: Wolfgang Bumiller --- src/PVE/Syscall.pm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/PVE/Syscall.pm b/src/PVE/Syscall.pm index 516e408..2d5019f 100644 --- a/src/PVE/Syscall.pm +++ b/src/PVE/Syscall.pm @@ -90,3 +90,5 @@ sub file_handle_result($) { return $fh; } + +1; -- 2.20.1 ___ pve-devel mailing list pve-devel@pve.proxmox.com https://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
[pve-devel] [PATCH common 3/5] PVE::Syscall: add new mount api constants
Signed-off-by: Wolfgang Bumiller --- src/PVE/Syscall.pm | 69 +- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/src/PVE/Syscall.pm b/src/PVE/Syscall.pm index 99e43e7..516e408 100644 --- a/src/PVE/Syscall.pm +++ b/src/PVE/Syscall.pm @@ -1,6 +1,7 @@ package PVE::Syscall; my %syscalls; +my %fsmount_constants; BEGIN { die "syscall.ph can only be required once!\n" if $INC{'syscall.ph'}; require("syscall.ph"); @@ -15,11 +16,77 @@ BEGIN { faccessat => &SYS_faccessat, setresuid => &SYS_setresuid, fchownat => &SYS_fchownat, + mount => &SYS_mount, + + # These use asm-generic, so they're the same across (sane) architectures. We use numbers + # since they're not in perl's syscall.ph yet... + open_tree => 428, + move_mount => 429, + fsopen => 430, + fsconfig => 431, + fsmount => 432, + fspick => 433, +); + +%fsmount_constants = ( + OPEN_TREE_CLONE => 0x_0001, + OPEN_TREE_CLOEXEC => 000200_, # octal! + + MOVE_MOUNT_F_SYMLINKS => 0x_0001, + MOVE_MOUNT_F_AUTOMOUNTS => 0x_0002, + MOVE_MOUNT_F_EMPTY_PATH => 0x_0004, + MOVE_MOUNT_F_MASK => 0x_0007, + + MOVE_MOUNT_T_SYMLINKS => 0x_0010, + MOVE_MOUNT_T_AUTOMOUNTS => 0x_0020, + MOVE_MOUNT_T_EMPTY_PATH => 0x_0040, + MOVE_MOUNT_T_MASK => 0x_0070, + + FSMOUNT_CLOEXEC => 0x_0001, + + FSOPEN_CLOEXEC => 0x_0001, + + MOUNT_ATTR_RDONLY => 0x_0001, + MOUNT_ATTR_NOSUID => 0x_0002, + MOUNT_ATTR_NODEV => 0x_0004, + MOUNT_ATTR_NOEXEC => 0x_0008, + MOUNT_ATTR_RELATIME=> 0x_, + MOUNT_ATTR_NOATIME => 0x_0010, + MOUNT_ATTR_STRICTATIME => 0x_0020, + MOUNT_ATTR_NODIRATIME => 0x_0080, + + FSPICK_CLOEXEC => 0x_0001, + FSPICK_SYMLINK_NOFOLLOW => 0x_0002, + FSPICK_NO_AUTOMOUNT => 0x_0004, + FSPICK_EMPTY_PATH => 0x_0008, + + FSCONFIG_SET_FLAG=> 0, + FSCONFIG_SET_STRING => 1, + FSCONFIG_SET_BINARY => 2, + FSCONFIG_SET_PATH=> 3, + FSCONFIG_SET_PATH_EMPTY => 4, + FSCONFIG_SET_FD => 5, + FSCONFIG_CMD_CREATE => 6, + FSCONFIG_CMD_RECONFIGURE => 7, ); }; use constant \%syscalls; +use constant \%fsmount_constants; use base 'Exporter'; -our @EXPORT_OK = keys(%syscalls); +our @EXPORT_OK = (keys(%syscalls), keys(%fsmount_constants), 'file_handle_result'); +our %EXPORT_TAGS = (fsmount => [keys(%fsmount_constants)]); + +# Create a file handle from a numeric file descriptor (to make sure it's close()d when it goes out +# of scope). +sub file_handle_result($) { +my ($fd_num) = @_; +return undef if $fd_num < 0; + +open(my $fh, '<&=', $fd_num) + or return undef; + +return $fh; +} -- 2.20.1 ___ pve-devel mailing list pve-devel@pve.proxmox.com https://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
[pve-devel] [PATCH common 1/5] ProcFSTools: include ppid in read_proc_pid_stat
Signed-off-by: Wolfgang Bumiller --- src/PVE/ProcFSTools.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/src/PVE/ProcFSTools.pm b/src/PVE/ProcFSTools.pm index 40e4063..14c1d6e 100644 --- a/src/PVE/ProcFSTools.pm +++ b/src/PVE/ProcFSTools.pm @@ -150,6 +150,7 @@ sub read_proc_pid_stat { if ($statstr && $statstr =~ m/^$pid \(.*\) (\S) (-?\d+) -?\d+ -?\d+ -?\d+ -?\d+ \d+ \d+ \d+ \d+ \d+ (\d+) (\d+) (-?\d+) (-?\d+) -?\d+ -?\d+ -?\d+ 0 (\d+) (\d+) (-?\d+) \d+ \d+ \d+ \d+ \d+ \d+ \d+ \d+ \d+ \d+ \d+ \d+ \d+ -?\d+ -?\d+ \d+ \d+ \d+/) { return { status => $1, + ppid => $2, utime => $3, stime => $4, starttime => $7, -- 2.20.1 ___ pve-devel mailing list pve-devel@pve.proxmox.com https://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
[pve-devel] [PATCH container 7/8] config: vmconfig_apply_pending_mountpoint helper
for reuse in hotplug code Signed-off-by: Wolfgang Bumiller --- src/PVE/LXC/Config.pm | 65 ++- 1 file changed, 39 insertions(+), 26 deletions(-) diff --git a/src/PVE/LXC/Config.pm b/src/PVE/LXC/Config.pm index 39de691..44d7f93 100644 --- a/src/PVE/LXC/Config.pm +++ b/src/PVE/LXC/Config.pm @@ -1242,15 +1242,6 @@ sub vmconfig_apply_pending { warn $err_msg; }; -my $rescan_volume = sub { - my ($mp) = @_; - eval { - $mp->{size} = PVE::Storage::volume_size_info($storecfg, $mp->{volume}, 5) - if !defined($mp->{size}); - }; - warn "Could not rescan volume size - $@\n" if $@; -}; - my $pending_delete_hash = $class->parse_pending_delete($conf->{pending}->{delete}); # FIXME: $force deletion is not implemented for CTs foreach my $opt (sort keys %$pending_delete_hash) { @@ -1281,23 +1272,7 @@ sub vmconfig_apply_pending { next if $selection && !$selection->{$opt}; eval { if ($opt =~ m/^mp(\d+)$/) { - my $mp = $class->parse_ct_mountpoint($conf->{pending}->{$opt}); - my $old = $conf->{$opt}; - if ($mp->{type} eq 'volume') { - if ($mp->{volume} =~ $PVE::LXC::NEW_DISK_RE) { - PVE::LXC::create_disks($storecfg, $vmid, { $opt => $conf->{pending}->{$opt} }, $conf, 1); - } else { - $rescan_volume->($mp); - $conf->{pending}->{$opt} = $class->print_ct_mountpoint($mp); - } - } - if (defined($old)) { - my $mp = $class->parse_ct_mountpoint($old); - if ($mp->{type} eq 'volume') { - $class->add_unused_volume($conf, $mp->{volume}) - if !$class->is_volume_in_use($conf, $conf->{$opt}, 1, 1); - } - } + $class->vmconfig_apply_pending_mountpoint($vmid, $conf, $opt, $storecfg, 0); } elsif ($opt =~ m/^net(\d+)$/) { my $netid = $1; my $net = $class->parse_lxc_network($conf->{pending}->{$opt}); @@ -1315,6 +1290,44 @@ sub vmconfig_apply_pending { $class->write_config($vmid, $conf); } +my $rescan_volume = sub { +my ($storecfg, $mp) = @_; +eval { + $mp->{size} = PVE::Storage::volume_size_info($storecfg, $mp->{volume}, 5) + if !defined($mp->{size}); +}; +warn "Could not rescan volume size - $@\n" if $@; +}; + +sub vmconfig_apply_pending_mountpoint { +my ($class, $vmid, $conf, $opt, $storecfg, $running) = @_; + +my $mp = $class->parse_ct_mountpoint($conf->{pending}->{$opt}); +my $old = $conf->{$opt}; +if ($mp->{type} eq 'volume') { + if ($mp->{volume} =~ $PVE::LXC::NEW_DISK_RE) { + my $vollist = PVE::LXC::create_disks( + $storecfg, + $vmid, + { $opt => $conf->{pending}->{$opt} }, + $conf, + 1, + ); + } else { + $rescan_volume->($storecfg, $mp); + $conf->{pending}->{$opt} = $class->print_ct_mountpoint($mp); + } +} + +if (defined($old)) { + my $mp = $class->parse_ct_mountpoint($old); + if ($mp->{type} eq 'volume') { + $class->add_unused_volume($conf, $mp->{volume}) + if !$class->is_volume_in_use($conf, $conf->{$opt}, 1, 1); + } +} +} + sub classify_mountpoint { my ($class, $vol) = @_; if ($vol =~ m!^/!) { -- 2.20.1 ___ pve-devel mailing list pve-devel@pve.proxmox.com https://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
[pve-devel] [PATCH common 2/5] tools: add AT_FDCWD and extend exports
Signed-off-by: Wolfgang Bumiller --- src/PVE/Tools.pm | 11 ++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/PVE/Tools.pm b/src/PVE/Tools.pm index 076c18a..801977d 100644 --- a/src/PVE/Tools.pm +++ b/src/PVE/Tools.pm @@ -50,6 +50,14 @@ file_copy get_host_arch O_PATH O_TMPFILE +AT_EMPTY_PATH +AT_FDCWD +CLONE_NEWNS +CLONE_NEWUTS +CLONE_NEWIPC +CLONE_NEWUSER +CLONE_NEWPID +CLONE_NEWNET ); my $pvelogdir = "/var/log/pve"; @@ -86,7 +94,8 @@ use constant {CLONE_NEWNS => 0x0002, use constant {O_PATH=> 0x0020, O_TMPFILE => 0x0041}; # This includes O_DIRECTORY -use constant {AT_EMPTY_PATH => 0x1000}; +use constant {AT_EMPTY_PATH => 0x1000, + AT_FDCWD => -100}; sub run_with_timeout { my ($timeout, $code, @param) = @_; -- 2.20.1 ___ pve-devel mailing list pve-devel@pve.proxmox.com https://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
[pve-devel] [PATCH container 6/8] prestart-hook: use staged mountpoints on newer kernels
This way we operate on defined paths in the monitor namespace (/run/pve/mountpoint/{rootfs,mp0,mp1,...}) while performing the mount, and can use `move_mount()` without passing the MOVE_MOUNT_T_SYMLINKS flag when putting the hierarchy in place. Signed-off-by: Wolfgang Bumiller --- src/lxc-pve-prestart-hook | 79 +-- 1 file changed, 68 insertions(+), 11 deletions(-) diff --git a/src/lxc-pve-prestart-hook b/src/lxc-pve-prestart-hook index c0965ab..d01c202 100755 --- a/src/lxc-pve-prestart-hook +++ b/src/lxc-pve-prestart-hook @@ -5,9 +5,10 @@ package lxc_pve_prestart_hook; use strict; use warnings; -use POSIX; +use Errno qw(ENOSYS); +use Fcntl qw(O_DIRECTORY :mode); use File::Path; -use Fcntl ':mode'; +use POSIX; use PVE::Cluster; use PVE::LXC::Config; @@ -15,7 +16,8 @@ use PVE::LXC::Setup; use PVE::LXC::Tools; use PVE::LXC; use PVE::Storage; -use PVE::Tools; +use PVE::Syscall qw(:fsmount); +use PVE::Tools qw(AT_FDCWD O_PATH); PVE::LXC::Tools::lxc_hook('pre-start', 'lxc', sub { my ($vmid, $vars, undef, undef) = @_; @@ -51,19 +53,74 @@ PVE::LXC::Tools::lxc_hook('pre-start', 'lxc', sub { my (undef, $rootuid, $rootgid) = PVE::LXC::parse_id_maps($conf); -my $setup_mountpoint = sub { - my ($ms, $mountpoint) = @_; - - #return if $ms eq 'rootfs'; - my (undef, undef, $dev) = PVE::LXC::mountpoint_mount($mountpoint, $rootdir, $storage_cfg, undef, $rootuid, $rootgid); - push @$devices, $dev if $dev && $mountpoint->{quota}; -}; - # Unmount first when the user mounted the container with "pct mount". eval { PVE::Tools::run_command(['umount', '--recursive', $rootdir], outfunc => sub {}, errfunc => sub {}); }; +if (PVE::Tools::fsopen(0, 0)) { + die "kernel behaved unexpectedly: fsopen(NULL, 0) did not fail!\n"; +} + +my $setup_mountpoint; +if ($! == ENOSYS) { + # Legacy mode for old kernels: + $setup_mountpoint = sub { + my ($opt, $mountpoint) = @_; + + my (undef, undef, $dev) = PVE::LXC::mountpoint_mount( + $mountpoint, + $rootdir, + $storage_cfg, + undef, + $rootuid, + $rootgid, + ); + push @$devices, $dev if $dev && $mountpoint->{quota}; + }; +} else { + # With newer kernels we stage mount points and then use move_mount(). + my $rootdir_fd = undef; + $setup_mountpoint = sub { + my ($opt, $mountpoint) = @_; + + my $dir = PVE::LXC::get_staging_mount_path($opt); + my (undef, undef, $dev, $mount_fd) = PVE::LXC::mountpoint_stage( + $mountpoint, + $dir, + $storage_cfg, + undef, + $rootuid, + $rootgid, + ); + + my ($dfd, $ddir); + if ($rootdir_fd) { + $dfd = fileno($rootdir_fd); + $ddir = './' . $mountpoint->{mp}; + } else { + # Assert that 'rootfs' is the first one: + die "foreach_mount() error\n" if $opt ne 'rootfs'; + + # $rootdir is not controlled by the container, so we can use it directly + $dfd = AT_FDCWD; + $ddir = $rootdir; + } + + # NOTE: when we have openat2() available, even better: use that with fd-as-chroot mode + # and MOVE_MOUNT_T_EMPTY_PATH... + PVE::Tools::move_mount(fileno($mount_fd), '', $dfd, $ddir, &MOVE_MOUNT_F_EMPTY_PATH) + or die "failed to move '$opt' into container hierarchy: $!\n"; + + # From now on we mount inside our rootfs: + if (!$rootdir_fd) { + $rootdir_fd = $mount_fd; + } + + push @$devices, $dev if $dev && $mountpoint->{quota}; + }; +} + PVE::LXC::Config->foreach_mountpoint($conf, $setup_mountpoint); my $lxc_setup = PVE::LXC::Setup->new($conf, $rootdir); -- 2.20.1 ___ pve-devel mailing list pve-devel@pve.proxmox.com https://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
[pve-devel] [PATCH ct/common] mount point hotplugging & new mount api
The pve-common path of this patch set should be straight forward: minor additions to ProcFSTools and Tools, as well as the new mount api constants added to Syscall.pm. The container part then makes use of the new mount api in case the currently running kernel supports it. The hope for the future would be to simplify the code a bit once we can stop supporting kernels older than 5.2. For now, it starts with the ability to stage a mount point, and then moves on to changing the startup process to use this. Previously, the startup goes through the mount points in order and mounts them directly at the target location. This is prone to symlink attacks (especially when using nested shared bind mounts). When staging a mount in a fixed directory first, we can pick it up afterwards with the new `open_tree()` syscall, and move it in place with the new `move_mount()` syscall, which can work relative to directory file descriptors and has flags for whether or not the paths are allowed to follow symlinks. (In the future this can be hardened even more using `openat2()` using the container's root directory as "implicit chroot" while looking up the target directory and then issuing a `move_mount()` right onto the resulting path file descriptor via `MOVE_MOUNT_T_EMPTY_PATH`.) The main advantage of the new API however, is that we can pick up the mounts as file descriptors, then switch into the running container's mount namespace and `move_mount()` the mount point in place, without having to rely on an existing MS_SHARED mount point "hack". Hence the final patch adds support for mount point hotplugging - but only hotplug, not un-plug, since unmounting has a lot of issues (open file descriptors, unshared MS_PRIVATE mount namespaces referencing the mount (as well as those namespaces opened as file descriptors...), mounts having been moved (if they were previously hotplugged at least), ...). Wolfgang Bumiller (8): implement "staged mountpoints" add open_pid_fd, open_lxc_pid, open_ppid helpers split open_namespace out of enter_namespace add get_container_namespace helper add mount stage directory helpers prestart-hook: use staged mountpoints on newer kernels config: vmconfig_apply_pending_mountpoint helper implement mountpoint hotplugging src/PVE/LXC.pm| 183 -- src/PVE/LXC/Config.pm | 87 -- src/lxc-pve-prestart-hook | 79 +--- 3 files changed, 304 insertions(+), 45 deletions(-) -- 2.20.1 ___ pve-devel mailing list pve-devel@pve.proxmox.com https://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
[pve-devel] [PATCH container 3/8] split open_namespace out of enter_namespace
Signed-off-by: Wolfgang Bumiller --- src/PVE/LXC.pm | 14 ++ 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/PVE/LXC.pm b/src/PVE/LXC.pm index 3bbaa36..d40f490 100644 --- a/src/PVE/LXC.pm +++ b/src/PVE/LXC.pm @@ -1032,12 +1032,18 @@ sub update_ipconfig { } +my $open_namespace = sub { +my ($vmid, $pid, $kind) = @_; +sysopen my $fd, "/proc/$pid/ns/$kind", O_RDONLY + or die "failed to open $kind namespace of container $vmid: $!\n"; +return $fd; +}; + my $enter_namespace = sub { -my ($vmid, $pid, $which, $type) = @_; -sysopen my $fd, "/proc/$pid/ns/$which", O_RDONLY - or die "failed to open $which namespace of container $vmid: $!\n"; +my ($vmid, $pid, $kind, $type) = @_; +my $fd = $open_namespace->($vmid, $pid, $kind); PVE::Tools::setns(fileno($fd), $type) - or die "failed to enter $which namespace of container $vmid: $!\n"; + or die "failed to enter $kind namespace of container $vmid: $!\n"; close $fd; }; -- 2.20.1 ___ pve-devel mailing list pve-devel@pve.proxmox.com https://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
[pve-devel] [PATCH container 8/8] implement mountpoint hotplugging
Signed-off-by: Wolfgang Bumiller --- src/PVE/LXC.pm| 44 +++ src/PVE/LXC/Config.pm | 24 ++- 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/src/PVE/LXC.pm b/src/PVE/LXC.pm index eb4313d..d59ae22 100644 --- a/src/PVE/LXC.pm +++ b/src/PVE/LXC.pm @@ -1628,6 +1628,50 @@ sub __mountpoint_mount { die "unsupported storage"; } +sub mountpoint_hotplug($$$) { +my ($vmid, $conf, $opt, $mp, $storage_cfg) = @_; + +my (undef, $rootuid, $rootgid) = PVE::LXC::parse_id_maps($conf); + +# We do the rest in a fork with an unshared mount namespace, since we're now going to 'stage' +# the mountpoint, then grab it, then move into the container's namespace, then mount it. + +PVE::Tools::run_fork(sub { + # Pin the container pid longer, we also need to get its monitor/parent: + my ($ct_pid, $ct_pidfd) = open_lxc_pid($vmid) + or die "failed to open pidfd of container $vmid\'s init process\n"; + + my ($monitor_pid, $monitor_pidfd) = open_ppid($ct_pid) + or die "failed to open pidfd of container $vmid\'s monitor process\n"; + + my $ct_mnt_ns = $get_container_namespace->($vmid, $ct_pid, 'mnt'); + my $monitor_mnt_ns = $get_container_namespace->($vmid, $monitor_pid, 'mnt'); + + # Change into the monitor's mount namespace. We "pin" the mount into the monitor's + # namespace for it to remain active there since the container will be able to unmount + # hotplugged mount points and thereby potentially free up loop devices, which is a security + # concern. + PVE::Tools::setns(fileno($monitor_mnt_ns), PVE::Tools::CLONE_NEWNS); + chdir('/') + or die "failed to change root directory within the monitor's mount namespace: $!\n"; + + my $dir = get_staging_mount_path($opt); + my $mount_fd = mountpoint_stage($mp, $dir, $storage_cfg, undef, $rootuid, $rootgid); + + 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"; + + PVE::Tools::move_mount( + fileno($mount_fd), + "", + &AT_FDCWD, + $mp->{mp}, + &MOVE_MOUNT_F_EMPTY_PATH, + ); +}); +} + # Create a directory in the mountpoint staging tempfs. sub get_staging_mount_path($) { my ($opt) = @_; diff --git a/src/PVE/LXC/Config.pm b/src/PVE/LXC/Config.pm index 44d7f93..587d6c2 100644 --- a/src/PVE/LXC/Config.pm +++ b/src/PVE/LXC/Config.pm @@ -1217,6 +1217,10 @@ sub vmconfig_hotplug_pending { if (!$hotplug_memory_done) { # don't call twice if both opts are passed $hotplug_memory->($conf->{pending}->{memory}, $conf->{pending}->{swap}); } + } elsif ($opt =~ m/^mp(\d+)$/) { + $class->vmconfig_apply_pending_mountpoint($vmid, $conf, $opt, $storecfg, 1); + # vmconfig_apply_pending_mountpoint modifies the value if it creates a new disk + $value = $conf->{pending}->{$opt}; } else { die "skip\n"; # skip non-hotpluggable } @@ -1306,14 +1310,32 @@ sub vmconfig_apply_pending_mountpoint { my $old = $conf->{$opt}; if ($mp->{type} eq 'volume') { if ($mp->{volume} =~ $PVE::LXC::NEW_DISK_RE) { + my $original_value = $conf->{pending}->{$opt}; my $vollist = PVE::LXC::create_disks( $storecfg, $vmid, - { $opt => $conf->{pending}->{$opt} }, + { $opt => $original_value }, $conf, 1, ); + if ($running) { + # Re-parse mount point: + my $mp = $class->parse_ct_mountpoint($conf->{pending}->{$opt}); + eval { + PVE::LXC::mountpoint_hotplug($vmid, $conf, $opt, $mp, $storecfg); + }; + my $err = $@; + if ($err) { + PVE::LXC::destroy_disks($storecfg, $vollist); + # The pending-changes code collects errors but keeps on looping through further + # pending changes, so unroll the change in $conf as well if destroy_disks() + # didn't die(). + $conf->{pending}->{$opt} = $original_value; + die $err; + } + } } else { + die "skip\n" if $running; # TODO: "changing" mount points $rescan_volume->($storecfg, $mp); $conf->{pending}->{$opt} = $class->print_ct_mountpoint($mp); } -- 2.20.1 ___ pve-devel mailing list pve-devel@pve.proxmox.com https://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
Re: [pve-devel] [PATCH access-control 07/13] ticket: use clinfo to get cluster name
On 11/6/19 1:36 PM, Fabian Grünbichler wrote: > instead of parsing corosync.conf, and avoid coupling the access-control > API with PVE::Corosync. if corosync.conf and pmxcfs don't agree on how > the cluster is called, there is a bigger issue anyway.. hmm, but that's really not an excuse? Exactly at the time when I have issues in my physical datacenter I may want to be sure to operate on a node of the correct cluster. Or find out what cluster has issues when logging in.. IIRC, I explicitly used the corosync one as this was not an issue with that.. I mean, that one may naturally be wrecked too, but that needs manual intervention while a non-quorate situation is more likely.. But I tested this to see what happens with your patch. So quorum losses are not an issue itself, the clinfo is still kept there, after an pmxcfs restart with still staying unquorate the info will be lost though, but that's an acceptable trade-off IMO, so applied! > > Signed-off-by: Fabian Grünbichler > --- > PVE/API2/AccessControl.pm | 15 +++ > 1 file changed, 3 insertions(+), 12 deletions(-) > > diff --git a/PVE/API2/AccessControl.pm b/PVE/API2/AccessControl.pm > index 6d0ea82..c2324e8 100644 > --- a/PVE/API2/AccessControl.pm > +++ b/PVE/API2/AccessControl.pm > @@ -10,7 +10,6 @@ use PVE::Exception qw(raise raise_perm_exc); > use PVE::SafeSyslog; > use PVE::RPCEnvironment; > use PVE::Cluster qw(cfs_read_file); > -use PVE::Corosync; > use PVE::RESTHandler; > use PVE::AccessControl; > use PVE::JSONSchema qw(get_standard_option); > @@ -314,17 +313,9 @@ __PACKAGE__->register_method ({ > $res->{cap} = &$compute_api_permission($rpcenv, $username) > if !defined($res->{NeedTFA}); > > - if (PVE::Corosync::check_conf_exists(1)) { > - if ($rpcenv->check($username, '/', ['Sys.Audit'], 1)) { > - eval { > - my $conf = cfs_read_file('corosync.conf'); > - my $totem = PVE::Corosync::totem_config($conf); > - if ($totem->{cluster_name}) { > - $res->{clustername} = $totem->{cluster_name}; > - } > - }; > - warn "$@\n" if $@; > - } > + my $clinfo = PVE::Cluster::get_clinfo(); > + if ($clinfo->{cluster}->{name} && $rpcenv->check($username, '/', > ['Sys.Audit'], 1)) { > + $res->{clustername} = $clinfo->{cluster}->{name}; > } > > PVE::Cluster::log_msg('info', 'root@pam', "successful auth for user > '$username'"); > ___ pve-devel mailing list pve-devel@pve.proxmox.com https://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
Re: [pve-devel] [PATCHSET] pve-cluster split
On 11/6/19 1:36 PM, Fabian Grünbichler wrote: > some other cleanups / cruft removal / missing control metadata / etc.pp. is > also > included. I tried to keep general cleanups up-front, as always. > Applied those and other unproblematic ones, i.e.: common 1/2 cluster 02/16 cluster 03/16 cluster 05/16 cluster 06/16 cluster 07/16 access-control 01/13 access-control 02/13 access-control 03/13 access-control 04/13 access-control 05/13 access-control 06/13 access-control 07/13 access-control 10/13 storage 1/6 container 1/5 container 2/5 qemu-server 01/12 qemu-server 02/12 qemu-server 03/12 qemu-server 04/12 qemu-server 05/12 qemu-server 06/12 common got bumped for the cert fingerprint helper, the pve-tags format for Dominik's series was waiting for the bump anyway. Let's continue with this like discussed off-list, allowing to avoid a access-control split, and thus quite some churn. Much thanks for this! ___ pve-devel mailing list pve-devel@pve.proxmox.com https://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
Re: [pve-devel] [PATCH v3 kernel-meta] fix #2403: exclude initrd entries from /proc/cmdline
On Thu, Nov 07, 2019 at 08:43:17PM +0100, Thomas Lamprecht wrote: > On 10/16/19 1:17 PM, Oguz Bektas wrote: > > if we fallback to /proc/cmdline, it can include the booted initrd. > > > > to avoid loader entries with initrd 'options' lines, we have to parse > > them out. > > > > Signed-off-by: Oguz Bektas > > --- > > > > v2->v3: > > * match forward slashes > > * match underscore > > * match zero or more whitespace at the end > > > > efiboot/zz-pve-efiboot | 3 ++- > > 1 file changed, 2 insertions(+), 1 deletion(-) > > > > diff --git a/efiboot/zz-pve-efiboot b/efiboot/zz-pve-efiboot > > index 4756555..8771da9 100755 > > --- a/efiboot/zz-pve-efiboot > > +++ b/efiboot/zz-pve-efiboot > > @@ -50,7 +50,8 @@ update_esps() { > > CMDLINE="$(cat /etc/kernel/cmdline)" > > else > > warn "No /etc/kernel/cmdline found - falling back to > > /proc/cmdline" > > - CMDLINE="$(cat /proc/cmdline)" > > + # remove initrd entries > > + CMDLINE="$(awk '{gsub(/\yinitrd=([0-9a-zA-Z\/\\._-])*\s*/,x)}1' > > /proc/cmdline)" > > sooo, this does not works at all with the default installed mawk... > Only with gnu awk, which one may get fast installed on a developer > workstation... :/ So we all did not test clean environments.. :/ > > it seems mawk has no word boundary regexp[0], soo either we depend on gawk > or we find a way were it works with mawk.. > > [0]: https://mail-index.netbsd.org/tech-userlevel/2012/12/02/msg006954.html i looked around a bit but couldn't figure out a way to make it work with mawk. so maybe it's better to just use perl instead, to avoid having gawk dependency? perl -ne 's/\binitrd=([0-9a-zA-Z\\\/.-])*\s*//g; print;' /proc/cmdline > > > fi > > > > loop_esp_list update_esp_func > > > ___ pve-devel mailing list pve-devel@pve.proxmox.com https://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
Re: [pve-devel] [PATCH v3 kernel-meta] fix #2403: exclude initrd entries from /proc/cmdline
On 11/8/19 1:20 PM, Oguz Bektas wrote: > On Thu, Nov 07, 2019 at 08:43:17PM +0100, Thomas Lamprecht wrote: >> On 10/16/19 1:17 PM, Oguz Bektas wrote: >>> + CMDLINE="$(awk '{gsub(/\yinitrd=([0-9a-zA-Z\/\\._-])*\s*/,x)}1' >>> /proc/cmdline)" >> >> sooo, this does not works at all with the default installed mawk... >> Only with gnu awk, which one may get fast installed on a developer >> workstation... :/ So we all did not test clean environments.. > > :/ > >> >> it seems mawk has no word boundary regexp[0], soo either we depend on gawk >> or we find a way were it works with mawk.. >> >> [0]: https://mail-index.netbsd.org/tech-userlevel/2012/12/02/msg006954.html > > i looked around a bit but couldn't figure out a way to make it work with > mawk. so maybe it's better to just use perl instead, to avoid having > gawk dependency? > > perl -ne 's/\binitrd=([0-9a-zA-Z\\\/.-])*\s*//g; print;' /proc/cmdline > sounds good to me. FYI, you could also replace the print and -n switch with the -p switch: perl -pe 's/\binitrd=([0-9a-zA-Z\\\/.-])*\s*//g' /proc/cmdline see: perldoc perlrun ___ pve-devel mailing list pve-devel@pve.proxmox.com https://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
[pve-devel] [PATCH kernel-meta] use perl instead of (g)awk to clean /proc/cmdline
this awk line only works with gawk because of implementation differences between awk alternatives. debian has mawk installed by default, and mawk does not implement word boundary regex. to avoid having to depend on gawk, we can just use perl instead. Signed-off-by: Oguz Bektas --- efiboot/zz-pve-efiboot | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/efiboot/zz-pve-efiboot b/efiboot/zz-pve-efiboot index 1985d65..afdd665 100755 --- a/efiboot/zz-pve-efiboot +++ b/efiboot/zz-pve-efiboot @@ -51,7 +51,7 @@ update_esps() { else warn "No /etc/kernel/cmdline found - falling back to /proc/cmdline" # remove initrd entries - CMDLINE="$(awk '{ gsub(/\yinitrd=([0-9a-zA-Z\/\\._-])*\s*/, ""); print $0 }' /proc/cmdline)" + CMDLINE="$(perl -pe 's/\binitrd=([0-9a-zA-Z\\\/.-])*\s*//g;' /proc/cmdline)" fi loop_esp_list update_esp_func -- 2.20.1 ___ pve-devel mailing list pve-devel@pve.proxmox.com https://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
[pve-devel] applied: [PATCH kernel-meta] use perl instead of (g)awk to clean /proc/cmdline
On 11/8/19 1:47 PM, Oguz Bektas wrote: > this awk line only works with gawk because of implementation differences > between awk alternatives. > debian has mawk installed by default, and mawk does not implement word > boundary regex. to avoid having to depend on gawk, we can just use perl > instead. > > Signed-off-by: Oguz Bektas > --- > efiboot/zz-pve-efiboot | 2 +- > 1 file changed, 1 insertion(+), 1 deletion(-) > > diff --git a/efiboot/zz-pve-efiboot b/efiboot/zz-pve-efiboot > index 1985d65..afdd665 100755 > --- a/efiboot/zz-pve-efiboot > +++ b/efiboot/zz-pve-efiboot > @@ -51,7 +51,7 @@ update_esps() { > else > warn "No /etc/kernel/cmdline found - falling back to > /proc/cmdline" > # remove initrd entries > - CMDLINE="$(awk '{ gsub(/\yinitrd=([0-9a-zA-Z\/\\._-])*\s*/, > ""); print $0 }' /proc/cmdline)" > + CMDLINE="$(perl -pe 's/\binitrd=([0-9a-zA-Z\\\/.-])*\s*//g;' > /proc/cmdline)" > fi > > loop_esp_list update_esp_func > applied, thanks! ___ pve-devel mailing list pve-devel@pve.proxmox.com https://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
[pve-devel] applied: [PATCH kernel-meta] efiboot/autorm functions: ignore running kernel if it was removed
On 11/7/19 8:46 PM, Thomas Lamprecht wrote: > In the case were someone removes the current kernel we do not can > "keep" it anymore. While this was obviously no issue for the > autoremoval logic, it is an issue for the pve-efiboot-tool refresh > command, which reuses this helper to see which kernels it needs to > keep on the ESP. > > Without this a running kernel was never removed from the EFI System > Partitions if de-installed from a host, so if it sorted as newest one > it was then booted again, which naturally confuses users (it was just > removed!!). So to ensure that we cannot get such zombie kernels > ensure that only installed kernels are included in the list. > > Signed-off-by: Thomas Lamprecht > --- > > still not enough to make it fully work, as a previous fix (initrd entry > removal > from /proc/cmdline) doesn't work with `mawk` AWK (the default) but only with > `gawk` as it uses GNU AWK extensions > > efiboot/functions | 7 ++- > 1 file changed, 6 insertions(+), 1 deletion(-) > > diff --git a/efiboot/functions b/efiboot/functions > index a179713..b804fb9 100755 > --- a/efiboot/functions > +++ b/efiboot/functions > @@ -14,7 +14,7 @@ PMX_LOADER_CONF="loader/loader.conf" > # debian's apt package: > # > # Mark as not-for-autoremoval those kernel packages that are: > -# - the currently booted version > +# - the currently booted version, if still installed > # - the kernel version we've been called for > # - the latest kernel version (as determined by debian version number) > # - the second-latest kernel version > @@ -37,6 +37,11 @@ kernel_keep_versions() { > # ignore the currently running version if attempting a reproducible > build > if [ -n "${SOURCE_DATE_EPOCH}" ]; then > running_version="" > + elif [ ! -e "/boot/vmlinuz-$running_version" ]; then > + # ignore the current version if it got removed, the > "auto-remove" logic > + # will not be affected, because either it is installed and thus > we keep > + # it in the list, or it's already removed anyway > + running_version="" > fi > > latest_2_versions="$(echo "$sorted_list" | grep -E '^[^ ]+-pve' | head > -n2 )" > applied, as quite simple and it works well here. ___ pve-devel mailing list pve-devel@pve.proxmox.com https://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
[pve-devel] applied: [PATCH v3 docs] Add section for ZFS Special Device
On 11/7/19 12:06 PM, Fabian Ebner wrote: > Signed-off-by: Fabian Ebner > --- > > Changes from v2: > * Better example of when a special device is useful > * Don't mention special_small_blocks property in the first section, so it > is explained right when we use it for the first time > * Explain possible values for size right away > * Use a concrete value for size in the examples > > local-zfs.adoc | 53 ++ > 1 file changed, 53 insertions(+) > applied, with small followup, among others noting what the default record size is. Thanks! ___ pve-devel mailing list pve-devel@pve.proxmox.com https://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel