* [PATCH common/container/manager 0/8] implement per-mountpoint uid/gid mapping
@ 2026-02-23 13:04 Filip Schauer
2026-02-23 13:04 ` [PATCH common 1/8] tools: export O_CLOEXEC constant Filip Schauer
` (7 more replies)
0 siblings, 8 replies; 9+ messages in thread
From: Filip Schauer @ 2026-02-23 13:04 UTC (permalink / raw)
To: pve-devel
Add support for configuring UID/GID mappings on individual container
mount points without affecting the global container mapping.
A new "idmap" mount point option accepts space-separated mappings:
```
idmap=type:ct:host:len type:ct:host:len ...
```
type: can be either 'u' or 'g'
ct: ID as seen inside the container
host: corresponding ID on the host
len: number of consecutive IDs to map
Unmapped ranges inherit the container's ID mapping.
Example to pass through the host UID & GID 1005:
```
mp0: /mnt/data,mp=/data,idmap=u:1005:1005:1 g:1005:1005:1
```
This allows, for example, passing through a directory owned by a
specific user on the host to a specific user inside the container,
without changing the ownership on the host or configuring an ID mapping
for the entire container.
Build/Bump order:
* pve-common
* pve-container
* pve-manager
pve-common:
Filip Schauer (3):
tools: export O_CLOEXEC constant
syscall: add missing mount attribute constants
tools: add mount_setattr syscall
src/PVE/Syscall.pm | 3 +++
src/PVE/Tools.pm | 10 ++++++++++
2 files changed, 13 insertions(+)
pve-container:
Filip Schauer (4):
namespaces: relax prototype of run_in_userns
namespaces: refactor run_in_userns
namespaces: add helper to create user namespace from idmap
implement per-mountpoint uid/gid mapping
src/PVE/LXC.pm | 85 ++++++++++++++++++++++++++++++++++++++-
src/PVE/LXC/Config.pm | 7 ++++
src/PVE/LXC/Namespaces.pm | 63 ++++++++++++++++++++++++-----
src/lxc-pve-prestart-hook | 14 +++++++
4 files changed, 157 insertions(+), 12 deletions(-)
pve-manager:
Filip Schauer (1):
ui: lxc/MPEdit: add "idmap" option
www/manager6/lxc/MPEdit.js | 203 +++++++++++++++++++++++++++++++++++++
1 file changed, 203 insertions(+)
Summary over all repositories:
7 files changed, 373 insertions(+), 12 deletions(-)
--
Generated by git-murpp 0.6.0
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH common 1/8] tools: export O_CLOEXEC constant
2026-02-23 13:04 [PATCH common/container/manager 0/8] implement per-mountpoint uid/gid mapping Filip Schauer
@ 2026-02-23 13:04 ` Filip Schauer
2026-02-23 13:04 ` [PATCH common 2/8] syscall: add missing mount attribute constants Filip Schauer
` (6 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Filip Schauer @ 2026-02-23 13:04 UTC (permalink / raw)
To: pve-devel
Signed-off-by: Filip Schauer <f.schauer@proxmox.com>
---
src/PVE/Tools.pm | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/PVE/Tools.pm b/src/PVE/Tools.pm
index 39c7155..d226fb6 100644
--- a/src/PVE/Tools.pm
+++ b/src/PVE/Tools.pm
@@ -55,6 +55,7 @@ our @EXPORT_OK = qw(
file_copy
get_host_arch
O_PATH
+ O_CLOEXEC
O_TMPFILE
AT_EMPTY_PATH
AT_FDCWD
--
2.47.3
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH common 2/8] syscall: add missing mount attribute constants
2026-02-23 13:04 [PATCH common/container/manager 0/8] implement per-mountpoint uid/gid mapping Filip Schauer
2026-02-23 13:04 ` [PATCH common 1/8] tools: export O_CLOEXEC constant Filip Schauer
@ 2026-02-23 13:04 ` Filip Schauer
2026-02-23 13:04 ` [PATCH common 3/8] tools: add mount_setattr syscall Filip Schauer
` (5 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Filip Schauer @ 2026-02-23 13:04 UTC (permalink / raw)
To: pve-devel
Signed-off-by: Filip Schauer <f.schauer@proxmox.com>
---
src/PVE/Syscall.pm | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/PVE/Syscall.pm b/src/PVE/Syscall.pm
index 7a931d7..a5753d9 100644
--- a/src/PVE/Syscall.pm
+++ b/src/PVE/Syscall.pm
@@ -67,6 +67,8 @@ BEGIN {
MOUNT_ATTR_NOATIME => 0x0000_0010,
MOUNT_ATTR_STRICTATIME => 0x0000_0020,
MOUNT_ATTR_NODIRATIME => 0x0000_0080,
+ MOUNT_ATTR_IDMAP => 0x0010_0000,
+ MOUNT_ATTR_NOSYMFOLLOW => 0x0020_0000,
FSPICK_CLOEXEC => 0x0000_0001,
FSPICK_SYMLINK_NOFOLLOW => 0x0000_0002,
--
2.47.3
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH common 3/8] tools: add mount_setattr syscall
2026-02-23 13:04 [PATCH common/container/manager 0/8] implement per-mountpoint uid/gid mapping Filip Schauer
2026-02-23 13:04 ` [PATCH common 1/8] tools: export O_CLOEXEC constant Filip Schauer
2026-02-23 13:04 ` [PATCH common 2/8] syscall: add missing mount attribute constants Filip Schauer
@ 2026-02-23 13:04 ` Filip Schauer
2026-02-23 13:04 ` [PATCH container 4/8] namespaces: relax prototype of run_in_userns Filip Schauer
` (4 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Filip Schauer @ 2026-02-23 13:04 UTC (permalink / raw)
To: pve-devel
The mount_setattr syscall can change attributes of an existing mount.
Signed-off-by: Filip Schauer <f.schauer@proxmox.com>
---
src/PVE/Syscall.pm | 1 +
src/PVE/Tools.pm | 9 +++++++++
2 files changed, 10 insertions(+)
diff --git a/src/PVE/Syscall.pm b/src/PVE/Syscall.pm
index a5753d9..53e0f4b 100644
--- a/src/PVE/Syscall.pm
+++ b/src/PVE/Syscall.pm
@@ -27,6 +27,7 @@ BEGIN {
renameat2 => &SYS_renameat2,
open_tree => &SYS_open_tree,
move_mount => &SYS_move_mount,
+ mount_setattr => &SYS_mount_setattr,
fsopen => &SYS_fsopen,
fsconfig => &SYS_fsconfig,
fsmount => &SYS_fsmount,
diff --git a/src/PVE/Tools.pm b/src/PVE/Tools.pm
index d226fb6..5b15874 100644
--- a/src/PVE/Tools.pm
+++ b/src/PVE/Tools.pm
@@ -1682,6 +1682,15 @@ sub move_mount($$$$$) {
);
}
+sub mount_setattr($$$$$$$) {
+ my ($dirfd, $path, $flags, $attr_set, $attr_clr, $propagation, $userns_fd) = @_;
+
+ my $attr = pack("Q4", $attr_set, $attr_clr, $propagation, $userns_fd);
+ return 0 ==
+ syscall(&PVE::Syscall::mount_setattr, int($dirfd), $path, int($flags), $attr,
+ length($attr));
+}
+
sub fsopen($$) {
my ($fsname, $flags) = @_;
return PVE::Syscall::file_handle_result(syscall(&PVE::Syscall::fsopen, $fsname, int($flags)));
--
2.47.3
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH container 4/8] namespaces: relax prototype of run_in_userns
2026-02-23 13:04 [PATCH common/container/manager 0/8] implement per-mountpoint uid/gid mapping Filip Schauer
` (2 preceding siblings ...)
2026-02-23 13:04 ` [PATCH common 3/8] tools: add mount_setattr syscall Filip Schauer
@ 2026-02-23 13:04 ` Filip Schauer
2026-02-23 13:04 ` [PATCH container 5/8] namespaces: refactor run_in_userns Filip Schauer
` (3 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Filip Schauer @ 2026-02-23 13:04 UTC (permalink / raw)
To: pve-devel
Allow passing a coderef stored in a private variable as the $code
argument. This fixes the following compile-time error:
Type of arg 1 to PVE::LXC::Namespaces::run_in_userns must be block or
sub {} (not private variable) at PVE/LXC/Create.pm line 736
Signed-off-by: Filip Schauer <f.schauer@proxmox.com>
---
src/PVE/LXC/Namespaces.pm | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/PVE/LXC/Namespaces.pm b/src/PVE/LXC/Namespaces.pm
index aa62659..3b86262 100644
--- a/src/PVE/LXC/Namespaces.pm
+++ b/src/PVE/LXC/Namespaces.pm
@@ -25,7 +25,7 @@ my sub set_id_map($$) {
PVE::Tools::run_command(['newuidmap', $pid, @uid_args]) if scalar(@uid_args);
}
-sub run_in_userns(&;$) {
+sub run_in_userns($;$) {
my ($code, $id_map) = @_;
socketpair(my $sp, my $sc, AF_UNIX, SOCK_STREAM, PF_UNSPEC)
or die "socketpair: $!\n";
--
2.47.3
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH container 5/8] namespaces: refactor run_in_userns
2026-02-23 13:04 [PATCH common/container/manager 0/8] implement per-mountpoint uid/gid mapping Filip Schauer
` (3 preceding siblings ...)
2026-02-23 13:04 ` [PATCH container 4/8] namespaces: relax prototype of run_in_userns Filip Schauer
@ 2026-02-23 13:04 ` Filip Schauer
2026-02-23 13:04 ` [PATCH container 6/8] namespaces: add helper to create user namespace from idmap Filip Schauer
` (2 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Filip Schauer @ 2026-02-23 13:04 UTC (permalink / raw)
To: pve-devel
Signed-off-by: Filip Schauer <f.schauer@proxmox.com>
---
src/PVE/LXC/Namespaces.pm | 27 +++++++++++++++++++--------
1 file changed, 19 insertions(+), 8 deletions(-)
diff --git a/src/PVE/LXC/Namespaces.pm b/src/PVE/LXC/Namespaces.pm
index 3b86262..477d0ac 100644
--- a/src/PVE/LXC/Namespaces.pm
+++ b/src/PVE/LXC/Namespaces.pm
@@ -25,6 +25,19 @@ my sub set_id_map($$) {
PVE::Tools::run_command(['newuidmap', $pid, @uid_args]) if scalar(@uid_args);
}
+my sub sync_send {
+ my ($fh, $msg) = @_;
+
+ syswrite($fh, $msg) == length($msg) or die "sync write failed: $!\n";
+}
+
+my sub sync_recv {
+ my ($fh, $expect) = @_;
+
+ my $received = <$fh>;
+ die "sync read failed\n" if $received ne $expect;
+}
+
sub run_in_userns($;$) {
my ($code, $id_map) = @_;
socketpair(my $sp, my $sc, AF_UNIX, SOCK_STREAM, PF_UNSPEC)
@@ -32,25 +45,23 @@ sub run_in_userns($;$) {
my $child = sub {
close($sp);
PVE::Tools::unshare(CLONE_NEWUSER | CLONE_NEWNS) or die "unshare(NEWUSER|NEWNS): $!\n";
- syswrite($sc, "1\n") == 2 or die "write: $!\n";
+ sync_send($sc, "1\n");
shutdown($sc, 1);
- my $two = <$sc>;
- die "failed to sync with parent process\n" if $two ne "2\n";
+ sync_recv($sc, "2\n");
close($sc);
$! = undef;
($(, $)) = (0, 0);
- die "$!\n" if $!;
+ die "setgid(0): $!\n" if $!;
($<, $>) = (0, 0);
- die "$!\n" if $!;
+ die "setuid(0): $!\n" if $!;
return $code->();
};
my $parent = sub {
my ($pid) = @_;
close($sc);
- my $one = <$sp>;
- die "failed to sync with userprocess\n" if $one ne "1\n";
+ sync_recv($sp, "1\n");
set_id_map($pid, $id_map);
- syswrite($sp, "2\n") == 2 or die "write: $!\n";
+ sync_send($sp, "2\n");
close($sp);
};
PVE::Tools::run_fork($child, { afterfork => $parent });
--
2.47.3
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH container 6/8] namespaces: add helper to create user namespace from idmap
2026-02-23 13:04 [PATCH common/container/manager 0/8] implement per-mountpoint uid/gid mapping Filip Schauer
` (4 preceding siblings ...)
2026-02-23 13:04 ` [PATCH container 5/8] namespaces: refactor run_in_userns Filip Schauer
@ 2026-02-23 13:04 ` Filip Schauer
2026-02-23 13:04 ` [PATCH container 7/8] implement per-mountpoint uid/gid mapping Filip Schauer
2026-02-23 13:04 ` [PATCH manager 8/8] ui: lxc/MPEdit: add "idmap" option Filip Schauer
7 siblings, 0 replies; 9+ messages in thread
From: Filip Schauer @ 2026-02-23 13:04 UTC (permalink / raw)
To: pve-devel
Signed-off-by: Filip Schauer <f.schauer@proxmox.com>
---
src/PVE/LXC/Namespaces.pm | 34 ++++++++++++++++++++++++++++++++--
1 file changed, 32 insertions(+), 2 deletions(-)
diff --git a/src/PVE/LXC/Namespaces.pm b/src/PVE/LXC/Namespaces.pm
index 477d0ac..7836d06 100644
--- a/src/PVE/LXC/Namespaces.pm
+++ b/src/PVE/LXC/Namespaces.pm
@@ -3,10 +3,10 @@ package PVE::LXC::Namespaces;
use strict;
use warnings;
-use Fcntl qw(O_WRONLY);
+use Fcntl qw(O_WRONLY O_RDONLY);
use Socket;
-use PVE::Tools qw(CLONE_NEWNS CLONE_NEWUSER);
+use PVE::Tools qw(CLONE_NEWNS CLONE_NEWUSER O_CLOEXEC);
my sub set_id_map($$) {
my ($pid, $id_map) = @_;
@@ -67,4 +67,34 @@ sub run_in_userns($;$) {
PVE::Tools::run_fork($child, { afterfork => $parent });
}
+# Create a new user namespace with the provided idmap applied.
+# Returns a file handle to the namespace.
+sub new_userns($) {
+ my ($id_map) = @_;
+ socketpair(my $sp, my $sc, AF_UNIX, SOCK_STREAM, PF_UNSPEC)
+ or die "socketpair: $!\n";
+ my $userns_fh;
+ my $child = sub {
+ close($sp);
+ PVE::Tools::unshare(CLONE_NEWUSER) or die "unshare(NEWUSER): $!\n";
+ sync_send($sc, "1\n");
+ shutdown($sc, 1);
+ sync_recv($sc, "2\n");
+ close($sc);
+ };
+ my $parent = sub {
+ my ($pid) = @_;
+ close($sc);
+ sync_recv($sp, "1\n");
+ set_id_map($pid, $id_map);
+ sysopen($userns_fh, "/proc/$pid/ns/user", O_RDONLY | O_CLOEXEC)
+ or die "Failed to open user namespace of child: $!\n";
+ sync_send($sp, "2\n");
+ close($sp);
+ };
+ PVE::Tools::run_fork($child, { afterfork => $parent });
+
+ return $userns_fh;
+}
+
1;
--
2.47.3
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH container 7/8] implement per-mountpoint uid/gid mapping
2026-02-23 13:04 [PATCH common/container/manager 0/8] implement per-mountpoint uid/gid mapping Filip Schauer
` (5 preceding siblings ...)
2026-02-23 13:04 ` [PATCH container 6/8] namespaces: add helper to create user namespace from idmap Filip Schauer
@ 2026-02-23 13:04 ` Filip Schauer
2026-02-23 13:04 ` [PATCH manager 8/8] ui: lxc/MPEdit: add "idmap" option Filip Schauer
7 siblings, 0 replies; 9+ messages in thread
From: Filip Schauer @ 2026-02-23 13:04 UTC (permalink / raw)
To: pve-devel
Add support for customizing UID/GID mappings on individual mount points
without affecting the entire container.
A new "idmap" mount point option accepts space-separated mappings:
```
idmap=type:ct:host:len type:ct:host:len ...
```
type: can be either 'u' or 'g'
ct: ID as seen inside the container
host: corresponding ID on the host
len: number of consecutive IDs to map
Unmapped ranges inherit the container's ID mapping.
Example to pass through the host UID & GID 1005:
```
mp0: /mnt/data,mp=/data,idmap=u:1005:1005:1 g:1005:1005:1
```
Signed-off-by: Filip Schauer <f.schauer@proxmox.com>
---
src/PVE/LXC.pm | 85 ++++++++++++++++++++++++++++++++++++++-
src/PVE/LXC/Config.pm | 7 ++++
src/lxc-pve-prestart-hook | 14 +++++++
3 files changed, 105 insertions(+), 1 deletion(-)
diff --git a/src/PVE/LXC.pm b/src/PVE/LXC.pm
index 2c02e9a..ec7cb01 100644
--- a/src/PVE/LXC.pm
+++ b/src/PVE/LXC.pm
@@ -11,6 +11,7 @@ use File::Path;
use File::Spec;
use IO::Poll qw(POLLIN POLLHUP);
use IO::Socket::UNIX;
+use List::Util qw(max min);
use POSIX qw(EINTR);
use Socket;
use Time::HiRes qw (gettimeofday);
@@ -43,6 +44,7 @@ use PVE::Syscall qw(:fsmount);
use PVE::LXC::CGroup;
use PVE::LXC::Config;
use PVE::LXC::Monitor;
+use PVE::LXC::Namespaces;
use PVE::LXC::Tools;
my $have_sdn;
@@ -2438,7 +2440,12 @@ sub device_passthrough_hotplug : prototype($$$) {
sub mountpoint_hotplug : prototype($$$$$) {
my ($vmid, $conf, $opt, $mp, $storage_cfg) = @_;
- my (undef, $root_uid, $root_gid) = PVE::LXC::parse_id_maps($conf);
+ my ($id_map, $root_uid, $root_gid) = PVE::LXC::parse_id_maps($conf);
+ my $mp_userns_fh;
+ if ($mp->{idmap}) {
+ my $mp_id_map = parse_mountpoint_idmap($id_map, $mp);
+ $mp_userns_fh = PVE::LXC::Namespaces::new_userns($mp_id_map);
+ }
# We do the rest in a fork with an unshared mount namespace:
# -) change our apparmor profile to 'pve-container-mounthotplug', which is '/usr/bin/lxc-start'
@@ -2474,6 +2481,18 @@ sub mountpoint_hotplug : prototype($$$$$) {
my $mount_fd = mountpoint_stage($mp, $dir, $storage_cfg, undef, $root_uid, $root_gid);
+ if ($mp_userns_fh) {
+ PVE::Tools::mount_setattr(
+ fileno($mount_fd),
+ '',
+ PVE::Tools::AT_EMPTY_PATH,
+ &PVE::Syscall::MOUNT_ATTR_IDMAP,
+ 0,
+ 0,
+ fileno($mp_userns_fh),
+ ) or die "mount_setattr: $!\n";
+ }
+
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";
@@ -2989,6 +3008,70 @@ sub map_ct_gid_to_host {
return map_ct_id_to_host($gid, $id_map, 'g');
}
+sub parse_mountpoint_idmap {
+ my ($id_map, $mp) = @_;
+
+ die "mount point does not specify an idmap\n" if !$mp->{idmap};
+
+ # Parse the user-friendly mount-specific ID map
+ # This maps IDs as seen in the container to IDs as seen on disk.
+ my $user_mp_id_map = [];
+ for my $entry (split(' ', $mp->{idmap})) {
+ $entry =~ /^([ug]):(\d+):(\d+):(\d+)$/ or die "failed to parse mount point idmap: $entry\n";
+ push @$user_mp_id_map, [$1, $2, $3, $4];
+ }
+
+ validate_id_maps($user_mp_id_map);
+
+ # Convert the user friendly mp.idmap to the actual mapping to be applied via mount_setattr.
+ # Provided by the config:
+ # lxc.idmap: ID in Container --> ID on Host
+ # mp.idmap: ID in Container --> ID on Disk
+ #
+ # Convert to: ID on Disk --> ID on Host
+ my $result = [];
+ for my $type ('u', 'g') {
+ my @ct_chunks = grep { $_->[0] eq $type } @$id_map;
+ next if !@ct_chunks;
+
+ my @exceptions = sort { $a->[1] <=> $b->[1] } grep { $_->[0] eq $type } @$user_mp_id_map;
+
+ for my $chunk (@ct_chunks) {
+ my (undef, $ct_start, $host_start, $len) = @$chunk;
+ my $ct_end = $ct_start + $len;
+
+ # Find exceptions that fall within this specific lxc.idmap chunk
+ my @chunk_exc = grep { $_->[1] < $ct_end && $_->[1] + $_->[3] > $ct_start } @exceptions;
+ push @chunk_exc, [$type, $ct_end, undef, 0]; # ensure the trailing gap is mapped
+
+ my $ct = $ct_start;
+ for my $exc (@chunk_exc) {
+ my (undef, $exc_ct, $exc_disk, $exc_len) = @$exc;
+
+ my $clamped_ct = max($exc_ct, $ct_start);
+ my $clamped_len = min($exc_ct + $exc_len, $ct_end) - $clamped_ct;
+
+ # Identity mapping for unmapped ranges
+ if ($ct < $clamped_ct) {
+ my $host = $host_start + ($ct - $ct_start);
+ push @$result, [$type, $host, $host, $clamped_ct - $ct];
+ }
+
+ # Map the IDs on Disk to the Host IDs.
+ if ($clamped_len > 0) {
+ my $disk = $exc_disk + $clamped_ct - $exc_ct;
+ my $host = $host_start + $clamped_ct - $ct_start;
+ push @$result, [$type, $disk, $host, $clamped_len];
+ }
+
+ $ct = $clamped_ct + $clamped_len;
+ }
+ }
+ }
+
+ return $result;
+}
+
sub userns_command {
my ($id_map) = @_;
if (@$id_map) {
diff --git a/src/PVE/LXC/Config.pm b/src/PVE/LXC/Config.pm
index 5442586..9f56bc7 100644
--- a/src/PVE/LXC/Config.pm
+++ b/src/PVE/LXC/Config.pm
@@ -369,6 +369,13 @@ my $rootfs_desc = {
format_description => 'opt[;opt...]',
pattern => qr/$valid_mount_option_re(;$valid_mount_option_re)*/,
},
+ idmap => {
+ optional => 1,
+ type => 'string',
+ description => 'Map specific UIDs/GIDs to specific host UIDs/GIDs for this mount point',
+ format_description => 'id-type:id-mount:id-host:id-range id-type:id-mount:...',
+ pattern => qr/^(?:[ug]:[0-9]+:[0-9]+:[1-9][0-9]*)(?: [ug]:[0-9]+:[0-9]+:[1-9][0-9]*)*$/,
+ },
ro => {
type => 'boolean',
description => 'Read-only mount point',
diff --git a/src/lxc-pve-prestart-hook b/src/lxc-pve-prestart-hook
index 9862509..6e500a8 100755
--- a/src/lxc-pve-prestart-hook
+++ b/src/lxc-pve-prestart-hook
@@ -95,6 +95,20 @@ PVE::LXC::Tools::lxc_hook(
$mountpoint, $dir, $storage_cfg, undef, $root_uid, $root_gid,
);
+ if ($mountpoint->{idmap}) {
+ my $mp_id_map = PVE::LXC::parse_mountpoint_idmap($id_map, $mountpoint);
+ my $usernsfh = PVE::LXC::Namespaces::new_userns($mp_id_map);
+ PVE::Tools::mount_setattr(
+ fileno($mount_fd),
+ '',
+ PVE::Tools::AT_EMPTY_PATH,
+ &PVE::Syscall::MOUNT_ATTR_IDMAP,
+ 0,
+ 0,
+ fileno($usernsfh),
+ ) or die "mount_setattr: $!\n";
+ }
+
my ($dest_dir, $dest_base_fd, $keep_attrs);
if ($rootdir_fd) {
# Mount relative to the rootdir fd.
--
2.47.3
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH manager 8/8] ui: lxc/MPEdit: add "idmap" option
2026-02-23 13:04 [PATCH common/container/manager 0/8] implement per-mountpoint uid/gid mapping Filip Schauer
` (6 preceding siblings ...)
2026-02-23 13:04 ` [PATCH container 7/8] implement per-mountpoint uid/gid mapping Filip Schauer
@ 2026-02-23 13:04 ` Filip Schauer
7 siblings, 0 replies; 9+ messages in thread
From: Filip Schauer @ 2026-02-23 13:04 UTC (permalink / raw)
To: pve-devel
Signed-off-by: Filip Schauer <f.schauer@proxmox.com>
---
www/manager6/lxc/MPEdit.js | 203 +++++++++++++++++++++++++++++++++++++
1 file changed, 203 insertions(+)
diff --git a/www/manager6/lxc/MPEdit.js b/www/manager6/lxc/MPEdit.js
index 4ed2d07b..b07f8882 100644
--- a/www/manager6/lxc/MPEdit.js
+++ b/www/manager6/lxc/MPEdit.js
@@ -48,6 +48,7 @@ Ext.define('PVE.lxc.MountPointInputPanel', {
setMPOpt('acl', values.acl);
setMPOpt('replicate', values.replicate);
setMPOpt('keepattrs', values.keepattrs);
+ setMPOpt('idmap', values.idmap);
let res = {};
res[confid] = PVE.Parser.printLxcMountPoint(me.mp);
@@ -132,6 +133,12 @@ Ext.define('PVE.lxc.MountPointInputPanel', {
me.getViewModel().set('type', rec.data.type);
},
},
+ 'grid proxmoxintegerfield': {
+ change: 'idmapChanged',
+ },
+ 'field[name=idmap]': {
+ change: 'setGridData',
+ },
},
init: function (view) {
let _me = this;
@@ -154,6 +161,90 @@ Ext.define('PVE.lxc.MountPointInputPanel', {
view.setVMConfig(view.vmconfig);
}
},
+ idmapChanged: function (field, value) {
+ let me = this;
+ if (value === null) {
+ return;
+ }
+ let record = field.getWidgetRecord();
+ if (record === undefined) {
+ return;
+ }
+ let col = field.getWidgetColumn();
+ record.set(col.dataIndex, value);
+ record.commit();
+
+ me.updateIDMapField();
+ },
+ idmapTypeChanged: function (button, newValue) {
+ let me = this;
+ let record = button.getWidgetRecord();
+ if (record === undefined || record.get('type') === newValue) {
+ return;
+ }
+ record.set('type', newValue);
+ record.commit();
+ me.updateIDMapField();
+ },
+ addIDMap: function () {
+ let me = this;
+ me.lookup('idmaps').getStore().add({
+ type: 'u',
+ ct: '',
+ host: '',
+ length: '',
+ });
+
+ me.updateIDMapField();
+ },
+ removeIDMap: function (field) {
+ let me = this;
+ let record = field.getWidgetRecord();
+ if (record === undefined) {
+ return;
+ }
+
+ me.lookup('idmaps').getStore().remove(record);
+ me.updateIDMapField();
+ },
+ updateIDMapField: function () {
+ let me = this;
+
+ let idmaps = [];
+ me.lookup('idmaps')
+ .getStore()
+ .each((rec) => {
+ let { type, ct, host, length } = rec.data;
+ idmaps.push(`${type}:${ct}:${host}:${length}`);
+ });
+
+ let field = me.lookup('idmap');
+ field.suspendEvent('change');
+ field.setValue(idmaps.join(' '));
+ field.resumeEvent('change');
+ },
+ parseIDMap: function (idmap) {
+ let [type, ct, host, length] = idmap.split(':');
+
+ let record = {
+ type,
+ ct,
+ host,
+ length,
+ };
+
+ return record;
+ },
+ setGridData: function (field, value) {
+ let me = this;
+ if (!value) {
+ return;
+ }
+
+ value = value.split(' ');
+ let records = value.map((idmap) => me.parseIDMap(idmap));
+ me.lookup('idmaps').getStore().setData(records);
+ },
},
viewModel: {
@@ -354,6 +445,118 @@ Ext.define('PVE.lxc.MountPointInputPanel', {
},
},
],
+
+ advancedColumnB: [
+ {
+ xtype: 'displayfield',
+ fieldLabel: gettext('ID Mapping'),
+ },
+ {
+ xtype: 'fieldcontainer',
+ items: [
+ {
+ xtype: 'grid',
+ height: 200,
+ scrollable: true,
+ reference: 'idmaps',
+ viewConfig: {
+ emptyText: gettext('No ID maps configured'),
+ },
+ store: {
+ fields: ['type', 'ct', 'host', 'length'],
+ data: [],
+ },
+ columns: [
+ {
+ text: gettext('ID Type'),
+ xtype: 'widgetcolumn',
+ dataIndex: 'type',
+ widget: {
+ xtype: 'segmentedbutton',
+ items: [
+ {
+ text: 'UID',
+ value: 'u',
+ },
+ {
+ text: 'GID',
+ value: 'g',
+ },
+ ],
+ listeners: {
+ change: 'idmapTypeChanged',
+ },
+ },
+ flex: 1,
+ },
+ {
+ text: gettext('Container'),
+ xtype: 'widgetcolumn',
+ dataIndex: 'ct',
+ widget: {
+ xtype: 'proxmoxintegerfield',
+ margin: '4 0',
+ emptyText: gettext('Container'),
+ isFormField: false,
+ allowBlank: false,
+ minValue: 0,
+ },
+ flex: 1,
+ },
+ {
+ text: gettext('Host'),
+ xtype: 'widgetcolumn',
+ dataIndex: 'host',
+ widget: {
+ xtype: 'proxmoxintegerfield',
+ margin: '4 0',
+ emptyText: gettext('Host'),
+ isFormField: false,
+ allowBlank: false,
+ minValue: 0,
+ },
+ flex: 1,
+ },
+ {
+ text: gettext('Length'),
+ xtype: 'widgetcolumn',
+ dataIndex: 'length',
+ widget: {
+ xtype: 'proxmoxintegerfield',
+ margin: '4 0',
+ emptyText: gettext('Length'),
+ isFormField: false,
+ allowBlank: false,
+ minValue: 1,
+ },
+ flex: 1,
+ },
+ {
+ xtype: 'widgetcolumn',
+ width: 40,
+ widget: {
+ xtype: 'button',
+ margin: '4 0',
+ iconCls: 'fa fa-trash-o',
+ handler: 'removeIDMap',
+ },
+ },
+ ],
+ },
+ ],
+ },
+ {
+ xtype: 'button',
+ text: gettext('Add'),
+ iconCls: 'fa fa-plus-circle',
+ handler: 'addIDMap',
+ },
+ {
+ xtype: 'hidden',
+ reference: 'idmap',
+ name: 'idmap',
+ },
+ ],
});
Ext.define('PVE.lxc.MountPointEdit', {
--
2.47.3
^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2026-02-23 13:10 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-02-23 13:04 [PATCH common/container/manager 0/8] implement per-mountpoint uid/gid mapping Filip Schauer
2026-02-23 13:04 ` [PATCH common 1/8] tools: export O_CLOEXEC constant Filip Schauer
2026-02-23 13:04 ` [PATCH common 2/8] syscall: add missing mount attribute constants Filip Schauer
2026-02-23 13:04 ` [PATCH common 3/8] tools: add mount_setattr syscall Filip Schauer
2026-02-23 13:04 ` [PATCH container 4/8] namespaces: relax prototype of run_in_userns Filip Schauer
2026-02-23 13:04 ` [PATCH container 5/8] namespaces: refactor run_in_userns Filip Schauer
2026-02-23 13:04 ` [PATCH container 6/8] namespaces: add helper to create user namespace from idmap Filip Schauer
2026-02-23 13:04 ` [PATCH container 7/8] implement per-mountpoint uid/gid mapping Filip Schauer
2026-02-23 13:04 ` [PATCH manager 8/8] ui: lxc/MPEdit: add "idmap" option Filip Schauer
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.