public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
* [pve-devel] [PATCH qemu-server docs manager] Implement support for fw_cfg
@ 2023-03-01  9:12 Leo Nunner
  2023-03-01  9:12 ` [pve-devel] [PATCH qemu-server 1/2] fix #4068: implement " Leo Nunner
                   ` (3 more replies)
  0 siblings, 4 replies; 7+ messages in thread
From: Leo Nunner @ 2023-03-01  9:12 UTC (permalink / raw)
  To: pve-devel

This patch introduces an interface for passing values to a guest via the
fw_cfg parameter. Settings are in the format

    opt/$key=$value

Where $key should start with a rfqdn (but doesn't need to). Both plain
strings and files (only from the snippets directory) can be passed through.
An example application for this parameter is for provisioning CoreOS
systems [1].

Files are currently passed through if the user specifies the value as
"storage:snippets/file", there is no implicit file flag.

[1] https://coreos.github.io/ignition/

qemu-server:

Leo Nunner (2):
  fix #4068: implement support for fw_cfg
  test: add cfg2cmd tests for fw_cfg

 PVE/API2/Qemu.pm                     | 14 +++++++++++
 PVE/QemuServer.pm                    | 35 ++++++++++++++++++++++++++++
 test/cfg2cmd/fw_cfg-files.conf       | 15 ++++++++++++
 test/cfg2cmd/fw_cfg-files.conf.cmd   | 30 ++++++++++++++++++++++++
 test/cfg2cmd/fw_cfg-strings.conf     | 15 ++++++++++++
 test/cfg2cmd/fw_cfg-strings.conf.cmd | 30 ++++++++++++++++++++++++
 6 files changed, 139 insertions(+)
 create mode 100644 test/cfg2cmd/fw_cfg-files.conf
 create mode 100644 test/cfg2cmd/fw_cfg-files.conf.cmd
 create mode 100644 test/cfg2cmd/fw_cfg-strings.conf
 create mode 100644 test/cfg2cmd/fw_cfg-strings.conf.cmd

docs:

Leo Nunner (1):
  fix #4068: document fw_cfg parameter

 qm.adoc | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

manager:

Leo Nunner (1):
  fix #4068: expose fw_cfg through the GUI

 www/manager6/Makefile                |   1 +
 www/manager6/qemu/FirmwareCfgEdit.js | 224 +++++++++++++++++++++++++++
 www/manager6/qemu/Options.js         |   6 +
 3 files changed, 231 insertions(+)
 create mode 100644 www/manager6/qemu/FirmwareCfgEdit.js

-- 
2.30.2





^ permalink raw reply	[flat|nested] 7+ messages in thread

* [pve-devel] [PATCH qemu-server 1/2] fix #4068: implement support for fw_cfg
  2023-03-01  9:12 [pve-devel] [PATCH qemu-server docs manager] Implement support for fw_cfg Leo Nunner
@ 2023-03-01  9:12 ` Leo Nunner
  2023-03-15 14:05   ` Wolfgang Bumiller
  2023-03-01  9:12 ` [pve-devel] [PATCH qemu-server 2/2] test: add cfg2cmd tests " Leo Nunner
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 7+ messages in thread
From: Leo Nunner @ 2023-03-01  9:12 UTC (permalink / raw)
  To: pve-devel

Implements support for passing values to the fw_cfg argument for QEMU.
If the value looks like a file, the backend checks for the correct
permissions/path and if everything is right, includes it as a file
instead of as a string.

Setting the argument requires the VM.Config.Options permission on the
guest. Including files requires Datastore.Audit and Datastore.Allocate
on the specific storage.

Signed-off-by: Leo Nunner <l.nunner@proxmox.com>
---
RFC: I feel like a more implicit option for passing files would be
nicer, but I can't really think of a nice way…

 PVE/API2/Qemu.pm  | 14 ++++++++++++++
 PVE/QemuServer.pm | 35 +++++++++++++++++++++++++++++++++++
 2 files changed, 49 insertions(+)

diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm
index 587bb22..c03394f 100644
--- a/PVE/API2/Qemu.pm
+++ b/PVE/API2/Qemu.pm
@@ -639,6 +639,8 @@ my $check_vm_modify_config_perm = sub {
 	    # the user needs Disk and PowerMgmt privileges to change the vmstate
 	    # also needs privileges on the storage, that will be checked later
 	    $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk', 'VM.PowerMgmt' ]);
+	} elsif ($opt eq 'fw_cfg') {
+	    $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
 	} else {
 	    # catches hostpci\d+, args, lock, etc.
 	    # new options will be checked here
@@ -1770,6 +1772,18 @@ my $update_vm_api  = sub {
 		} elsif ($opt eq 'tags') {
 		    assert_tag_permissions($vmid, $conf->{$opt}, $param->{$opt}, $rpcenv, $authuser);
 		    $conf->{pending}->{$opt} = PVE::GuestHelpers::get_unique_tags($param->{$opt});
+		} elsif ($opt eq 'fw_cfg') {
+		    foreach my $fw_cfg (PVE::Tools::split_list($param->{$opt})) {
+			my ($opt, $val) = split("=", $fw_cfg);
+
+			if (my $storage = PVE::Storage::parse_volume_id($val, 1)) {
+			    $rpcenv->check($authuser, "/storage/$storage", ['Datastore.Audit', 'Datastore.Allocate']);
+
+			    my ($path, undef, $type) = PVE::Storage::path($storecfg, $val);
+			    die "File $val is not in snippets directory\n" if $type ne "snippets";
+			}
+		    }
+		    $conf->{pending}->{$opt} = $param->{$opt};
 		} else {
 		    $conf->{pending}->{$opt} = $param->{$opt};
 
diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm
index 40be44d..c5dea1f 100644
--- a/PVE/QemuServer.pm
+++ b/PVE/QemuServer.pm
@@ -723,6 +723,12 @@ EODESCR
 	description => "List of host cores used to execute guest processes, for example: 0,5,8-11",
 	optional => 1,
     },
+    fw_cfg => {
+	type => 'string',
+	optional => 1,
+	description => 'Pass values to the guest via the fw_cfg parameter.',
+	format => 'pve-fw-cfg-list',
+    },
 };
 
 my $cicustom_fmt = {
@@ -1076,6 +1082,21 @@ sub verify_volume_id_or_absolute_path {
     return $volid;
 }
 
+PVE::JSONSchema::register_format('pve-fw-cfg', \&verify_fw_cfg);
+sub verify_fw_cfg {
+    my ($value, $noerr) = @_;
+
+    my $FW_CFG_REGEX = qr/[a-z0-9\.\/:\-]+/;
+
+    if ($value =~ m/^(opt\/$FW_CFG_REGEX)=($FW_CFG_REGEX)$/) {
+	return $value;
+    }
+
+    return if $noerr;
+
+    die "unable to parse fw_cfg option\n";
+}
+
 my $usb_fmt = {
     host => {
 	default_key => 1,
@@ -4181,6 +4202,20 @@ sub config_to_command {
 	push @$cmd, '-snapshot';
     }
 
+    if ($conf->{fw_cfg}) {
+	foreach my $conf (PVE::Tools::split_list($conf->{fw_cfg})) {
+	    my ($opt, $val) = split("=", $conf);
+
+	    push @$cmd, "-fw_cfg";
+	    if (PVE::Storage::parse_volume_id($val, 1)) {
+		my $path = PVE::Storage::path($storecfg, $val);
+		push @$cmd, "$opt,file=$path";
+	    } else {
+		push @$cmd, "$opt,string=$val";
+	    }
+	}
+    }
+
     # add custom args
     if ($conf->{args}) {
 	my $aa = PVE::Tools::split_args($conf->{args});
-- 
2.30.2





^ permalink raw reply	[flat|nested] 7+ messages in thread

* [pve-devel] [PATCH qemu-server 2/2] test: add cfg2cmd tests for fw_cfg
  2023-03-01  9:12 [pve-devel] [PATCH qemu-server docs manager] Implement support for fw_cfg Leo Nunner
  2023-03-01  9:12 ` [pve-devel] [PATCH qemu-server 1/2] fix #4068: implement " Leo Nunner
@ 2023-03-01  9:12 ` Leo Nunner
  2023-03-01  9:12 ` [pve-devel] [PATCH docs] fix #4068: document fw_cfg parameter Leo Nunner
  2023-03-01  9:12 ` [pve-devel] [PATCH manager] fix #4068: expose fw_cfg through the GUI Leo Nunner
  3 siblings, 0 replies; 7+ messages in thread
From: Leo Nunner @ 2023-03-01  9:12 UTC (permalink / raw)
  To: pve-devel

Signed-off-by: Leo Nunner <l.nunner@proxmox.com>
---
 test/cfg2cmd/fw_cfg-files.conf       | 15 ++++++++++++++
 test/cfg2cmd/fw_cfg-files.conf.cmd   | 30 ++++++++++++++++++++++++++++
 test/cfg2cmd/fw_cfg-strings.conf     | 15 ++++++++++++++
 test/cfg2cmd/fw_cfg-strings.conf.cmd | 30 ++++++++++++++++++++++++++++
 4 files changed, 90 insertions(+)
 create mode 100644 test/cfg2cmd/fw_cfg-files.conf
 create mode 100644 test/cfg2cmd/fw_cfg-files.conf.cmd
 create mode 100644 test/cfg2cmd/fw_cfg-strings.conf
 create mode 100644 test/cfg2cmd/fw_cfg-strings.conf.cmd

diff --git a/test/cfg2cmd/fw_cfg-files.conf b/test/cfg2cmd/fw_cfg-files.conf
new file mode 100644
index 0000000..a8117d1
--- /dev/null
+++ b/test/cfg2cmd/fw_cfg-files.conf
@@ -0,0 +1,15 @@
+# TEST: Config with file values for fw_cfg
+# QEMU_VERSION: 7.0.0
+bios: ovmf
+bootdisk: scsi0
+cores: 2
+efidisk0: local:100/vm-100-disk-1.qcow2,size=128K
+fw_cfg: opt/com.proxmox/test1=local:snippets/test1,opt/com.proxmox/test2=cifs-store:snippets/test2
+machine: q35
+meta: creation-qemu=6.1
+memory: 512
+net0: virtio=2E:01:68:F9:9C:87,bridge=vmbr0
+ostype: l26
+scsihw: virtio-scsi-pci
+smbios1: uuid=3dd750ce-d910-44d0-9493-525c0be4e687
+vmgenid: 54d1c06c-8f5b-440f-b5b2-6eab1380e13d
diff --git a/test/cfg2cmd/fw_cfg-files.conf.cmd b/test/cfg2cmd/fw_cfg-files.conf.cmd
new file mode 100644
index 0000000..7d14053
--- /dev/null
+++ b/test/cfg2cmd/fw_cfg-files.conf.cmd
@@ -0,0 +1,30 @@
+/usr/bin/kvm \
+  -id 8006 \
+  -name 'vm8006,debug-threads=on' \
+  -no-shutdown \
+  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \
+  -mon 'chardev=qmp,mode=control' \
+  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect=5' \
+  -mon 'chardev=qmp-event,mode=control' \
+  -pidfile /var/run/qemu-server/8006.pid \
+  -daemonize \
+  -smbios 'type=1,uuid=3dd750ce-d910-44d0-9493-525c0be4e687' \
+  -drive 'if=pflash,unit=0,format=raw,readonly=on,file=/usr/share/pve-edk2-firmware//OVMF_CODE.fd' \
+  -drive 'if=pflash,unit=1,id=drive-efidisk0,format=qcow2,file=/var/lib/vz/images/100/vm-100-disk-1.qcow2' \
+  -smp '2,sockets=1,cores=2,maxcpus=2' \
+  -nodefaults \
+  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \
+  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \
+  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \
+  -m 512 \
+  -readconfig /usr/share/qemu-server/pve-q35-4.0.cfg \
+  -device 'vmgenid,guid=54d1c06c-8f5b-440f-b5b2-6eab1380e13d' \
+  -device 'usb-tablet,id=tablet,bus=ehci.0,port=1' \
+  -device 'VGA,id=vga,bus=pcie.0,addr=0x1' \
+  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \
+  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \
+  -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/var/lib/qemu-server/pve-bridge,downscript=/var/lib/qemu-server/pve-bridgedown,vhost=on' \
+  -device 'virtio-net-pci,mac=2E:01:68:F9:9C:87,netdev=net0,bus=pci.0,addr=0x12,id=net0,bootindex=300' \
+  -machine 'type=q35+pve0' \
+  -fw_cfg 'opt/com.proxmox/test1,file=/var/lib/vz/snippets/test1' \
+  -fw_cfg 'opt/com.proxmox/test2,file=/mnt/pve/cifs-store/snippets/test2'
diff --git a/test/cfg2cmd/fw_cfg-strings.conf b/test/cfg2cmd/fw_cfg-strings.conf
new file mode 100644
index 0000000..1d3631b
--- /dev/null
+++ b/test/cfg2cmd/fw_cfg-strings.conf
@@ -0,0 +1,15 @@
+# TEST: Config with simple string values for fw_cfg
+# QEMU_VERSION: 7.0.0
+bios: ovmf
+bootdisk: scsi0
+cores: 2
+efidisk0: local:100/vm-100-disk-1.qcow2,size=128K
+fw_cfg: opt/com.proxmox/test1=first,opt/com.proxmox/test2=second
+machine: q35
+meta: creation-qemu=6.1
+memory: 512
+net0: virtio=2E:01:68:F9:9C:87,bridge=vmbr0
+ostype: l26
+scsihw: virtio-scsi-pci
+smbios1: uuid=3dd750ce-d910-44d0-9493-525c0be4e687
+vmgenid: 54d1c06c-8f5b-440f-b5b2-6eab1380e13d
diff --git a/test/cfg2cmd/fw_cfg-strings.conf.cmd b/test/cfg2cmd/fw_cfg-strings.conf.cmd
new file mode 100644
index 0000000..9ff7dc2
--- /dev/null
+++ b/test/cfg2cmd/fw_cfg-strings.conf.cmd
@@ -0,0 +1,30 @@
+/usr/bin/kvm \
+  -id 8006 \
+  -name 'vm8006,debug-threads=on' \
+  -no-shutdown \
+  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \
+  -mon 'chardev=qmp,mode=control' \
+  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect=5' \
+  -mon 'chardev=qmp-event,mode=control' \
+  -pidfile /var/run/qemu-server/8006.pid \
+  -daemonize \
+  -smbios 'type=1,uuid=3dd750ce-d910-44d0-9493-525c0be4e687' \
+  -drive 'if=pflash,unit=0,format=raw,readonly=on,file=/usr/share/pve-edk2-firmware//OVMF_CODE.fd' \
+  -drive 'if=pflash,unit=1,id=drive-efidisk0,format=qcow2,file=/var/lib/vz/images/100/vm-100-disk-1.qcow2' \
+  -smp '2,sockets=1,cores=2,maxcpus=2' \
+  -nodefaults \
+  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \
+  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \
+  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \
+  -m 512 \
+  -readconfig /usr/share/qemu-server/pve-q35-4.0.cfg \
+  -device 'vmgenid,guid=54d1c06c-8f5b-440f-b5b2-6eab1380e13d' \
+  -device 'usb-tablet,id=tablet,bus=ehci.0,port=1' \
+  -device 'VGA,id=vga,bus=pcie.0,addr=0x1' \
+  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \
+  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \
+  -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/var/lib/qemu-server/pve-bridge,downscript=/var/lib/qemu-server/pve-bridgedown,vhost=on' \
+  -device 'virtio-net-pci,mac=2E:01:68:F9:9C:87,netdev=net0,bus=pci.0,addr=0x12,id=net0,bootindex=300' \
+  -machine 'type=q35+pve0' \
+  -fw_cfg 'opt/com.proxmox/test1,string=first' \
+  -fw_cfg 'opt/com.proxmox/test2,string=second'
-- 
2.30.2





^ permalink raw reply	[flat|nested] 7+ messages in thread

* [pve-devel] [PATCH docs] fix #4068: document fw_cfg parameter
  2023-03-01  9:12 [pve-devel] [PATCH qemu-server docs manager] Implement support for fw_cfg Leo Nunner
  2023-03-01  9:12 ` [pve-devel] [PATCH qemu-server 1/2] fix #4068: implement " Leo Nunner
  2023-03-01  9:12 ` [pve-devel] [PATCH qemu-server 2/2] test: add cfg2cmd tests " Leo Nunner
@ 2023-03-01  9:12 ` Leo Nunner
  2023-03-01  9:12 ` [pve-devel] [PATCH manager] fix #4068: expose fw_cfg through the GUI Leo Nunner
  3 siblings, 0 replies; 7+ messages in thread
From: Leo Nunner @ 2023-03-01  9:12 UTC (permalink / raw)
  To: pve-devel

Signed-off-by: Leo Nunner <l.nunner@proxmox.com>
---
 qm.adoc | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --git a/qm.adoc b/qm.adoc
index bd535a2..0c587ad 100644
--- a/qm.adoc
+++ b/qm.adoc
@@ -1139,6 +1139,24 @@ http://localhost:9843 in a browser in the guest.
 
 It can help to restart the SPICE session.
 
+[[qm_fw_cfg]]
+QEMU Firmware Configuration
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+QEMU allows passing values to the guest via the 'fw_cfg' parameter. Settings
+are defined on a key/value basis, where the key follows a directory-like structure:
+
+----
+opt/com.proxmox/test1=local:snippets/config.txt
+opt/com.proxmox/test2=foobar
+----
+
+These settings can be edited in the GUI via 'Options' -> 'QEMU Firmware Configuration'.
+User-supplied keys *must* be prefixed with `opt/` and are recommended to start with a
+reverse fully qualified domain name. `opt/ovmf/` and `opt/org.qemu/` are reserved for
+internal use by QEMU and should not be set. Setting a value in the format
+`storage:snippets/file` causes the contents of the file to be included for the key.
+
 [[qm_migration]]
 Migration
 ---------
-- 
2.30.2





^ permalink raw reply	[flat|nested] 7+ messages in thread

* [pve-devel] [PATCH manager] fix #4068: expose fw_cfg through the GUI
  2023-03-01  9:12 [pve-devel] [PATCH qemu-server docs manager] Implement support for fw_cfg Leo Nunner
                   ` (2 preceding siblings ...)
  2023-03-01  9:12 ` [pve-devel] [PATCH docs] fix #4068: document fw_cfg parameter Leo Nunner
@ 2023-03-01  9:12 ` Leo Nunner
  3 siblings, 0 replies; 7+ messages in thread
From: Leo Nunner @ 2023-03-01  9:12 UTC (permalink / raw)
  To: pve-devel

Introduces a new UI element to set/edit/delete any number of fw_cfg
arguments in a table-like manner.

Signed-off-by: Leo Nunner <l.nunner@proxmox.com>
---
 www/manager6/Makefile                |   1 +
 www/manager6/qemu/FirmwareCfgEdit.js | 224 +++++++++++++++++++++++++++
 www/manager6/qemu/Options.js         |   6 +
 3 files changed, 231 insertions(+)
 create mode 100644 www/manager6/qemu/FirmwareCfgEdit.js

diff --git a/www/manager6/Makefile b/www/manager6/Makefile
index 05afeda4..bf3c8b20 100644
--- a/www/manager6/Makefile
+++ b/www/manager6/Makefile
@@ -217,6 +217,7 @@ JSSRC= 							\
 	qemu/Config.js					\
 	qemu/CreateWizard.js				\
 	qemu/DisplayEdit.js				\
+	qemu/FirmwareCfgEdit.js				\
 	qemu/HDEdit.js					\
 	qemu/HDEfi.js					\
 	qemu/HDTPM.js					\
diff --git a/www/manager6/qemu/FirmwareCfgEdit.js b/www/manager6/qemu/FirmwareCfgEdit.js
new file mode 100644
index 00000000..031042e3
--- /dev/null
+++ b/www/manager6/qemu/FirmwareCfgEdit.js
@@ -0,0 +1,224 @@
+Ext.define('pve-fw-cfg-option', {
+    extend: 'Ext.data.Model',
+    fields: [
+	{ name: 'opt', type: 'string' },
+	{ name: 'val', type: 'string' },
+    ],
+});
+
+Ext.define('PVE.qemu.FirmwareCfgPanel', {
+    extend: 'Ext.form.FieldContainer',
+    alias: 'widget.pveQemuFirmwareCfgPanel',
+
+    config: {}, // store loaded vm config
+    store: undefined,
+
+    inUpdate: false,
+    controller: {
+	xclass: 'Ext.app.ViewController',
+
+	init: function(view) {
+	    let me = this;
+	    let grid = me.lookup('grid');
+
+	    view.store = Ext.create('Ext.data.Store', {
+		model: 'pve-fw-cfg-option',
+	    });
+
+	    grid.setStore(view.store);
+	},
+
+	addOption: function() {
+	    let me = this;
+	    me.lookup('grid').getStore().add({});
+	},
+
+	removeOption: function(field) {
+	    let me = this;
+	    let record = field.getWidgetRecord();
+	    me.lookup('grid').getStore().remove(record);
+	    me.setMarkerValue();
+	},
+
+	onUpdate: function(record, property, value) {
+	    let me = this;
+
+	    if (record === undefined) {
+		return;
+	    }
+
+	    record.set(property, value);
+	    record.commit();
+	    me.setMarkerValue();
+	},
+
+	onUpdateOption: function(field, value) {
+	    let me = this;
+	    let record = field.getWidgetRecord();
+
+	    me.onUpdate(record, "opt", value);
+	},
+
+	onUpdateValue: function(field, value) {
+	    let me = this;
+	    let record = field.getWidgetRecord();
+
+	    me.onUpdate(record, "val", value);
+	},
+
+	setMarkerValue() {
+	    let me = this;
+	    let view = me.getView();
+
+	    view.inUpdate = true;
+	    me.lookup('marker').setValue(view.calculateValue());
+	    view.inUpdate = false;
+	},
+
+	control: {
+	    "grid textfield[dest=opt]": {
+		change: "onUpdateOption",
+	    },
+	    "grid textfield[dest=val]": {
+		change: "onUpdateValue",
+	    },
+	},
+    },
+
+    loadConfig: function(config) {
+	let me = this;
+	let marker = me.lookup('marker');
+	let list = PVE.Parser.parsePropertyString(config.fw_cfg);
+	let options = [];
+
+	me.config = config;
+	me.store.removeAll();
+
+	for (const [key, value] of Object.entries(list)) {
+	    options.push({
+		opt: key,
+		val: value,
+	    });
+	}
+
+	marker.originalValue = config.fw_cfg;
+	marker.value = config.fw_cfg;
+	me.store.setData(options);
+    },
+
+    calculateValue: function() {
+	let me = this;
+	let ret = [];
+	me.store.each((record) => {
+	    ret.push(record.data.opt + "=" + record.data.val);
+	});
+	return ret.join(",");
+    },
+
+    items: [
+	{
+	    xtype: 'grid',
+	    reference: 'grid',
+	    margin: '0 0 5 0',
+	    height: 150,
+	    defaults: {
+		sortable: false,
+		hideable: false,
+		draggable: false,
+	    },
+	    columns: [
+		{
+		    xtype: 'widgetcolumn',
+		    text: gettext('Option'),
+		    dataIndex: 'opt',
+		    flex: 1,
+		    isFormField: false,
+		    widget: {
+			xtype: 'textfield',
+			allowBlank: false,
+			dest: 'opt',
+			emptyText: 'opt/...',
+		    },
+		},
+		{
+		    xtype: 'widgetcolumn',
+		    text: gettext('Value'),
+		    dataIndex: 'val',
+		    flex: 1,
+		    isFormField: false,
+		    widget: {
+			xtype: 'textfield',
+			allowBlank: false,
+			dest: 'val',
+		    },
+		},
+		{
+		    xtype: 'widgetcolumn',
+		    width: 40,
+		    widget: {
+			xtype: 'button',
+			iconCls: 'fa fa-trash-o',
+			handler: 'removeOption',
+		    },
+		},
+	    ],
+	},
+	{
+	    // for dirty marking and 'reset' function
+	    xtype: 'hiddenfield',
+	    reference: 'marker',
+	    name: 'fw_cfg',
+	    setValue: function(value) {
+		let me = this;
+		let panel = me.up('pveQemuFirmwareCfgPanel');
+
+		// Reset
+		if (!panel.inUpdate) {
+		    panel.loadConfig(panel.config);
+		}
+
+		me.value = value;
+		me.checkDirty();
+	    },
+	    getValue: function() {
+		return this.value;
+	    },
+	    getSubmitValue: function() {
+		return this.value;
+	    },
+	},
+	{
+	    xtype: 'button',
+	    text: gettext('Add'),
+	    iconCls: 'fa fa-plus-circle',
+	    handler: 'addOption',
+	},
+    ],
+});
+
+Ext.define('PVE.qemu.FirmwareCfgEdit', {
+    extend: 'Proxmox.window.Edit',
+
+    items: [{
+	xtype: 'pveQemuFirmwareCfgPanel',
+	itemId: 'inputpanel',
+    }],
+
+    subject: gettext('Firmware Configuration'),
+    onlineHelp: 'qm_fw_cfg',
+    width: 640,
+
+    getValues: function() {
+	let me = this;
+	let values = me.callParent();
+	return values.fw_cfg ? values : { 'delete': 'fw_cfg' };
+    },
+
+    initComponent: function() {
+	let me = this;
+	me.callParent();
+	me.load({
+	    success: ({ result }) => me.down('#inputpanel').loadConfig(result.data),
+	});
+    },
+});
diff --git a/www/manager6/qemu/Options.js b/www/manager6/qemu/Options.js
index 7b112400..80407010 100644
--- a/www/manager6/qemu/Options.js
+++ b/www/manager6/qemu/Options.js
@@ -338,6 +338,12 @@ Ext.define('PVE.qemu.Options', {
 		    },
 		} : undefined,
 	    },
+	    fw_cfg: {
+		header: gettext('QEMU Firmware Configuration'),
+		defaultValue: '',
+		renderer: val => val || Proxmox.Utils.noneText,
+		editor: caps.vms['VM.Config.Options'] ? 'PVE.qemu.FirmwareCfgEdit' : undefined,
+	    },
 	    hookscript: {
 		header: gettext('Hookscript'),
 	    },
-- 
2.30.2





^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [pve-devel] [PATCH qemu-server 1/2] fix #4068: implement support for fw_cfg
  2023-03-01  9:12 ` [pve-devel] [PATCH qemu-server 1/2] fix #4068: implement " Leo Nunner
@ 2023-03-15 14:05   ` Wolfgang Bumiller
  2023-03-24  9:43     ` Leo Nunner
  0 siblings, 1 reply; 7+ messages in thread
From: Wolfgang Bumiller @ 2023-03-15 14:05 UTC (permalink / raw)
  To: Leo Nunner; +Cc: pve-devel

On Wed, Mar 01, 2023 at 10:12:26AM +0100, Leo Nunner wrote:
> Implements support for passing values to the fw_cfg argument for QEMU.
> If the value looks like a file, the backend checks for the correct
> permissions/path and if everything is right, includes it as a file
> instead of as a string.
> 
> Setting the argument requires the VM.Config.Options permission on the
> guest. Including files requires Datastore.Audit and Datastore.Allocate
> on the specific storage.
> 
> Signed-off-by: Leo Nunner <l.nunner@proxmox.com>
> ---
> RFC: I feel like a more implicit option for passing files would be
> nicer, but I can't really think of a nice way…
> 
>  PVE/API2/Qemu.pm  | 14 ++++++++++++++
>  PVE/QemuServer.pm | 35 +++++++++++++++++++++++++++++++++++
>  2 files changed, 49 insertions(+)
> 
> diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm
> index 587bb22..c03394f 100644
> --- a/PVE/API2/Qemu.pm
> +++ b/PVE/API2/Qemu.pm
> @@ -639,6 +639,8 @@ my $check_vm_modify_config_perm = sub {
>  	    # the user needs Disk and PowerMgmt privileges to change the vmstate
>  	    # also needs privileges on the storage, that will be checked later
>  	    $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk', 'VM.PowerMgmt' ]);
> +	} elsif ($opt eq 'fw_cfg') {
> +	    $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
>  	} else {
>  	    # catches hostpci\d+, args, lock, etc.
>  	    # new options will be checked here
> @@ -1770,6 +1772,18 @@ my $update_vm_api  = sub {
>  		} elsif ($opt eq 'tags') {
>  		    assert_tag_permissions($vmid, $conf->{$opt}, $param->{$opt}, $rpcenv, $authuser);
>  		    $conf->{pending}->{$opt} = PVE::GuestHelpers::get_unique_tags($param->{$opt});
> +		} elsif ($opt eq 'fw_cfg') {
> +		    foreach my $fw_cfg (PVE::Tools::split_list($param->{$opt})) {
> +			my ($opt, $val) = split("=", $fw_cfg);
> +
> +			if (my $storage = PVE::Storage::parse_volume_id($val, 1)) {
> +			    $rpcenv->check($authuser, "/storage/$storage", ['Datastore.Audit', 'Datastore.Allocate']);
> +
> +			    my ($path, undef, $type) = PVE::Storage::path($storecfg, $val);
> +			    die "File $val is not in snippets directory\n" if $type ne "snippets";
> +			}
> +		    }
> +		    $conf->{pending}->{$opt} = $param->{$opt};
>  		} else {
>  		    $conf->{pending}->{$opt} = $param->{$opt};
>  
> diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm
> index 40be44d..c5dea1f 100644
> --- a/PVE/QemuServer.pm
> +++ b/PVE/QemuServer.pm
> @@ -723,6 +723,12 @@ EODESCR
>  	description => "List of host cores used to execute guest processes, for example: 0,5,8-11",
>  	optional => 1,
>      },
> +    fw_cfg => {
> +	type => 'string',
> +	optional => 1,
> +	description => 'Pass values to the guest via the fw_cfg parameter.',
> +	format => 'pve-fw-cfg-list',
> +    },
>  };
>  
>  my $cicustom_fmt = {
> @@ -1076,6 +1082,21 @@ sub verify_volume_id_or_absolute_path {
>      return $volid;
>  }
>  
> +PVE::JSONSchema::register_format('pve-fw-cfg', \&verify_fw_cfg);
> +sub verify_fw_cfg {
> +    my ($value, $noerr) = @_;
> +
> +    my $FW_CFG_REGEX = qr/[a-z0-9\.\/:\-]+/;

IMO this is way too restrictive.  If we pass the `name=` prefix in the
`-fw_cfg` parameter we can even allow `=`.  In fact, AFAICT only `,` is
problematic and would need to be doubled. Also if we really just have a
single option with a list... both `,` and `;` will be problematic on our
end, so it's fine to exclude those 2 for now. (Until we add quoting
support to split_list ;-) )

That said, for the use cases I have in mind for us, the current code
would be sufficient, but at least uppercase letters, underscores and
spaces would be nice...

> +
> +    if ($value =~ m/^(opt\/$FW_CFG_REGEX)=($FW_CFG_REGEX)$/) {
> +	return $value;
> +    }
> +
> +    return if $noerr;
> +
> +    die "unable to parse fw_cfg option\n";
> +}
> +
>  my $usb_fmt = {
>      host => {
>  	default_key => 1,
> @@ -4181,6 +4202,20 @@ sub config_to_command {
>  	push @$cmd, '-snapshot';
>      }
>  
> +    if ($conf->{fw_cfg}) {
> +	foreach my $conf (PVE::Tools::split_list($conf->{fw_cfg})) {
> +	    my ($opt, $val) = split("=", $conf);
> +
> +	    push @$cmd, "-fw_cfg";
> +	    if (PVE::Storage::parse_volume_id($val, 1)) {
> +		my $path = PVE::Storage::path($storecfg, $val);
> +		push @$cmd, "$opt,file=$path";
> +	    } else {
> +		push @$cmd, "$opt,string=$val";

^ This would definitely be safer with the `name=` portion included.




^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [pve-devel] [PATCH qemu-server 1/2] fix #4068: implement support for fw_cfg
  2023-03-15 14:05   ` Wolfgang Bumiller
@ 2023-03-24  9:43     ` Leo Nunner
  0 siblings, 0 replies; 7+ messages in thread
From: Leo Nunner @ 2023-03-24  9:43 UTC (permalink / raw)
  To: Wolfgang Bumiller; +Cc: pve-devel

Thanks for the review!

On 2023-03-15 15:05, Wolfgang Bumiller wrote:
> On Wed, Mar 01, 2023 at 10:12:26AM +0100, Leo Nunner wrote:
>> Implements support for passing values to the fw_cfg argument for QEMU.
>> If the value looks like a file, the backend checks for the correct
>> permissions/path and if everything is right, includes it as a file
>> instead of as a string.
>>
>> Setting the argument requires the VM.Config.Options permission on the
>> guest. Including files requires Datastore.Audit and Datastore.Allocate
>> on the specific storage.
>>
>> Signed-off-by: Leo Nunner <l.nunner@proxmox.com>
>> ---
>> RFC: I feel like a more implicit option for passing files would be
>> nicer, but I can't really think of a nice way…
>>
>>  PVE/API2/Qemu.pm  | 14 ++++++++++++++
>>  PVE/QemuServer.pm | 35 +++++++++++++++++++++++++++++++++++
>>  2 files changed, 49 insertions(+)
>>
>> diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm
>> index 587bb22..c03394f 100644
>> --- a/PVE/API2/Qemu.pm
>> +++ b/PVE/API2/Qemu.pm
>> @@ -639,6 +639,8 @@ my $check_vm_modify_config_perm = sub {
>>  	    # the user needs Disk and PowerMgmt privileges to change the vmstate
>>  	    # also needs privileges on the storage, that will be checked later
>>  	    $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk', 'VM.PowerMgmt' ]);
>> +	} elsif ($opt eq 'fw_cfg') {
>> +	    $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
>>  	} else {
>>  	    # catches hostpci\d+, args, lock, etc.
>>  	    # new options will be checked here
>> @@ -1770,6 +1772,18 @@ my $update_vm_api  = sub {
>>  		} elsif ($opt eq 'tags') {
>>  		    assert_tag_permissions($vmid, $conf->{$opt}, $param->{$opt}, $rpcenv, $authuser);
>>  		    $conf->{pending}->{$opt} = PVE::GuestHelpers::get_unique_tags($param->{$opt});
>> +		} elsif ($opt eq 'fw_cfg') {
>> +		    foreach my $fw_cfg (PVE::Tools::split_list($param->{$opt})) {
>> +			my ($opt, $val) = split("=", $fw_cfg);
>> +
>> +			if (my $storage = PVE::Storage::parse_volume_id($val, 1)) {
>> +			    $rpcenv->check($authuser, "/storage/$storage", ['Datastore.Audit', 'Datastore.Allocate']);
>> +
>> +			    my ($path, undef, $type) = PVE::Storage::path($storecfg, $val);
>> +			    die "File $val is not in snippets directory\n" if $type ne "snippets";
>> +			}
>> +		    }
>> +		    $conf->{pending}->{$opt} = $param->{$opt};
>>  		} else {
>>  		    $conf->{pending}->{$opt} = $param->{$opt};
>>  
>> diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm
>> index 40be44d..c5dea1f 100644
>> --- a/PVE/QemuServer.pm
>> +++ b/PVE/QemuServer.pm
>> @@ -723,6 +723,12 @@ EODESCR
>>  	description => "List of host cores used to execute guest processes, for example: 0,5,8-11",
>>  	optional => 1,
>>      },
>> +    fw_cfg => {
>> +	type => 'string',
>> +	optional => 1,
>> +	description => 'Pass values to the guest via the fw_cfg parameter.',
>> +	format => 'pve-fw-cfg-list',
>> +    },
>>  };
>>  
>>  my $cicustom_fmt = {
>> @@ -1076,6 +1082,21 @@ sub verify_volume_id_or_absolute_path {
>>      return $volid;
>>  }
>>  
>> +PVE::JSONSchema::register_format('pve-fw-cfg', \&verify_fw_cfg);
>> +sub verify_fw_cfg {
>> +    my ($value, $noerr) = @_;
>> +
>> +    my $FW_CFG_REGEX = qr/[a-z0-9\.\/:\-]+/;
> IMO this is way too restrictive.  If we pass the `name=` prefix in the
> `-fw_cfg` parameter we can even allow `=`.  In fact, AFAICT only `,` is
> problematic and would need to be doubled. Also if we really just have a
> single option with a list... both `,` and `;` will be problematic on our
> end, so it's fine to exclude those 2 for now. (Until we add quoting
> support to split_list ;-) )
>
> That said, for the use cases I have in mind for us, the current code
> would be sufficient, but at least uppercase letters, underscores and
> spaces would be nice...
We could also just switch to something like `[^,;]+`, or would that be
too extreme?
>> +
>> +    if ($value =~ m/^(opt\/$FW_CFG_REGEX)=($FW_CFG_REGEX)$/) {
>> +	return $value;
>> +    }
>> +
>> +    return if $noerr;
>> +
>> +    die "unable to parse fw_cfg option\n";
>> +}
>> +
>>  my $usb_fmt = {
>>      host => {
>>  	default_key => 1,
>> @@ -4181,6 +4202,20 @@ sub config_to_command {
>>  	push @$cmd, '-snapshot';
>>      }
>>  
>> +    if ($conf->{fw_cfg}) {
>> +	foreach my $conf (PVE::Tools::split_list($conf->{fw_cfg})) {
>> +	    my ($opt, $val) = split("=", $conf);
>> +
>> +	    push @$cmd, "-fw_cfg";
>> +	    if (PVE::Storage::parse_volume_id($val, 1)) {
>> +		my $path = PVE::Storage::path($storecfg, $val);
>> +		push @$cmd, "$opt,file=$path";
>> +	    } else {
>> +		push @$cmd, "$opt,string=$val";
> ^ This would definitely be safer with the `name=` portion included.
Agreed, I'll change it accordingly.




^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2023-03-24  9:43 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-03-01  9:12 [pve-devel] [PATCH qemu-server docs manager] Implement support for fw_cfg Leo Nunner
2023-03-01  9:12 ` [pve-devel] [PATCH qemu-server 1/2] fix #4068: implement " Leo Nunner
2023-03-15 14:05   ` Wolfgang Bumiller
2023-03-24  9:43     ` Leo Nunner
2023-03-01  9:12 ` [pve-devel] [PATCH qemu-server 2/2] test: add cfg2cmd tests " Leo Nunner
2023-03-01  9:12 ` [pve-devel] [PATCH docs] fix #4068: document fw_cfg parameter Leo Nunner
2023-03-01  9:12 ` [pve-devel] [PATCH manager] fix #4068: expose fw_cfg through the GUI Leo Nunner

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal