use PVE::QemuServer::ImportDisk;
use PVE::QemuServer::Monitor qw(mon_cmd);
use PVE::QemuServer::Machine;
+use PVE::QemuServer::USB qw(parse_usb_device);
use PVE::QemuMigrate;
use PVE::RPCEnvironment;
use PVE::AccessControl;
return 1 if $authuser eq 'root@pam';
+ $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
+
my $device = PVE::JSONSchema::parse_property_string('pve-qm-usb', $value);
- if ($device->{host} =~ m/^spice$/i) {
- $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
- } else {
+ if ($device->{host} && $device->{host} !~ m/^spice$/i) {
die "only root can set '$opt' config for real devices\n";
+ } elsif ($device->{mapping}) {
+ $rpcenv->check_full($authuser, "/mapping/usb/$device->{mapping}", ['Mapping.Use']);
+ } else {
+ die "either 'host' or 'mapping' must be set.\n";
}
return 1;
my $oldconf = $snapname ? $conf->{snapshots}->{$snapname} : $conf;
my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
+ PVE::QemuServer::check_mapping_access($rpcenv, $authuser, $oldconf);
PVE::QemuServer::check_bridge_access($rpcenv, $authuser, $oldconf);
return 1;
};
+sub check_mapping_access {
+ my ($rpcenv, $user, $conf) = @_;
+
+ for my $opt (keys $conf->%*) {
+ if ($opt =~ m/^usb\d+$/) {
+ my $device = PVE::JSONSchema::parse_property_string('pve-qm-usb', $conf->{$opt});
+ if (my $host = $device->{host}) {
+ die "only root can set '$opt' config for real devices\n"
+ if $host !~ m/^spice$/i && $user ne 'root@pam';
+ } elsif ($device->{mapping}) {
+ $rpcenv->check_full($user, "/mapping/usb/$device->{mapping}", ['Mapping.Use']);
+ } else {
+ die "either 'host' or 'mapping' must be set.\n";
+ }
+ }
+ }
+};
+
+# FIXME: improve checks on restore by checking before actually extracing and
+# merging the new config
+sub check_restore_permissions {
+ my ($rpcenv, $user, $conf) = @_;
+ check_bridge_access($rpcenv, $user, $conf);
+ check_mapping_access($rpcenv, $user, $conf);
+}
# vzdump restore implementaion
sub tar_archive_read_firstfile {
}
my $new_conf = $restore_merge_config->($conffile, $new_conf_raw, $options->{override_conf});
- check_bridge_access($rpcenv, $user, $new_conf);
+ check_restore_permissions($rpcenv, $user, $new_conf);
PVE::QemuConfig->write_config($vmid, $new_conf);
eval { rescan($vmid, 1); };
}
my $new_conf = $restore_merge_config->($conffile, $new_conf_raw, $opts->{override_conf});
- check_bridge_access($rpcenv, $user, $new_conf);
+ check_restore_permissions($rpcenv, $user, $new_conf);
PVE::QemuConfig->write_config($vmid, $new_conf);
eval { rescan($vmid, 1); };
use PVE::QemuServer::Machine;
use PVE::QemuServer::Helpers qw(min_version windows_version);
use PVE::JSONSchema;
+use PVE::Mapping::USB;
use base 'Exporter';
our @EXPORT_OK = qw(
my $usb_fmt = {
host => {
default_key => 1,
+ optional => 1,
type => 'string',
pattern => qr/(?:(?:$USB_ID_RE)|(?:$USB_PATH_RE)|[Ss][Pp][Ii][Cc][Ee])/,
format_description => 'HOSTUSBDEVICE|spice',
machines - use with special care.
The value 'spice' can be used to add a usb redirection devices for spice.
+
+Either this or the 'mapping' key must be set.
EODESCR
},
+ mapping => {
+ optional => 1,
+ type => 'string',
+ format_description => 'mapping-id',
+ format => 'pve-configid',
+ description => "The ID of a cluster wide mapping. Either this or the default-key 'host'"
+ ." must be set.",
+ },
usb3 => {
optional => 1,
type => 'boolean',
PVE::JSONSchema::register_standard_option("pve-qm-usb", $usbdesc);
sub parse_usb_device {
- my ($value) = @_;
+ my ($value, $mapping) = @_;
- return if !$value;
+ return if $value && $mapping; # not a valid configuration
my $res = {};
- if ($value =~ m/^$USB_ID_RE$/) {
- $res->{vendorid} = $2;
- $res->{productid} = $4;
- } elsif ($value =~ m/^$USB_PATH_RE$/) {
- $res->{hostbus} = $1;
- $res->{hostport} = $2;
- } elsif ($value =~ m/^spice$/i) {
- $res->{spice} = 1;
- } else {
- return;
+ if (defined($value)) {
+ if ($value =~ m/^$USB_ID_RE$/) {
+ $res->{vendorid} = $2;
+ $res->{productid} = $4;
+ } elsif ($value =~ m/^$USB_PATH_RE$/) {
+ $res->{hostbus} = $1;
+ $res->{hostport} = $2;
+ } elsif ($value =~ m/^spice$/i) {
+ $res->{spice} = 1;
+ }
+ } elsif (defined($mapping)) {
+ my $devices = PVE::Mapping::USB::find_on_current_node($mapping);
+ die "USB device mapping not found for '$mapping'\n" if !$devices || !scalar($devices->@*);
+ die "More than one USB mapping per host not supported\n" if scalar($devices->@*) > 1;
+ eval {
+ PVE::Mapping::USB::assert_valid($mapping, $devices->[0]);
+ };
+ if (my $err = $@) {
+ die "USB Mapping invalid (hardware probably changed): $err\n";
+ }
+ my $device = $devices->[0];
+
+ if ($device->{path}) {
+ $res = parse_usb_device($device->{path});
+ } else {
+ $res = parse_usb_device($device->{id});
+ }
}
return $res;
$usbdevice .= ",port=$port" if defined($port);
}
- my $parsed = parse_usb_device($device->{host});
+ my $parsed = parse_usb_device($device->{host}, $device->{mapping});
if (defined($parsed->{vendorid}) && defined($parsed->{productid})) {
$usbdevice .= ",vendorid=0x$parsed->{vendorid},productid=0x$parsed->{productid}";