mirror of
https://git.proxmox.com/git/fwupd
synced 2025-04-28 21:07:41 +00:00
Add support for emulating and recording devices
Based on patches from Frédéric Danis <frederic.danis@collabora.com>
This commit is contained in:
parent
8bcb66745a
commit
b59c82e1bd
@ -65,6 +65,7 @@ cp $HOME/rpmbuild/RPMS/*/*.rpm dist
|
||||
|
||||
if [ "$CI" = "true" ]; then
|
||||
sed "s,^DisabledPlugins=.*,DisabledPlugins=," -i /etc/fwupd/daemon.conf
|
||||
sed "s,^AllowEmulation=false,AllowEmulation=true," -i /etc/fwupd/daemon.conf
|
||||
|
||||
# set up enough PolicyKit and D-Bus to run the daemon
|
||||
mkdir -p /run/dbus
|
||||
|
@ -4,9 +4,14 @@ _fwupdmgr_cmd_list=(
|
||||
'clear-results'
|
||||
'disable-remote'
|
||||
'device-test'
|
||||
'device-emulate'
|
||||
'downgrade'
|
||||
'download'
|
||||
'enable-remote'
|
||||
'emulation-tag'
|
||||
'emulation-untag'
|
||||
'emulation-load'
|
||||
'emulation-save'
|
||||
'get-approved-firmware'
|
||||
'get-bios-setting'
|
||||
'get-blocked-firmware'
|
||||
|
@ -52,6 +52,9 @@ OnlyTrusted=true
|
||||
# Show private data like device serial numbers and instance IDs to clients
|
||||
ShowDevicePrivate=true
|
||||
|
||||
# Allow capturing and loading device emulation
|
||||
AllowEmulation=false
|
||||
|
||||
# UIDs that should marked as trusted
|
||||
TrustedUids=
|
||||
|
||||
|
@ -4,6 +4,7 @@
|
||||
"steps": [
|
||||
{
|
||||
"url": "https://fwupd.org/downloads/5d860747c1378ef8921f95e41bbb7055dc9b470043655a65b00157ab74dd12e8-Analogix-ANX7518-fw-1.5.01-rx-1206.cab",
|
||||
"emulation-url": "https://fwupd.org/downloads/63698f1d36491d795f36335a28e301e7484b1735c63aa763c007c149cc74569c-Analogix-ANX7518-fw-1.5.01-rx-1206.zip",
|
||||
"components": [
|
||||
{
|
||||
"version": "0001.1501",
|
||||
@ -15,6 +16,7 @@
|
||||
},
|
||||
{
|
||||
"url": "https://fwupd.org/downloads/2e4b5747ea2659ddae893a7b8a238d6d9c3166b426c0d0e5feb308a443662c38-Analogix-ANX7518-fw-1.5.08.cab",
|
||||
"emulation-url": "https://fwupd.org/downloads/ff75670536d3de6def5cb4e6e1377dc1ef132882288dd6ceee37c0aecc3fbda3-Analogix-ANX7518-fw-1.5.08.zip",
|
||||
"components": [
|
||||
{
|
||||
"version": "0001.1508",
|
||||
|
@ -4,6 +4,7 @@
|
||||
"steps": [
|
||||
{
|
||||
"url": "https://fwupd.org/downloads/dbeeba12332296ae68eaa104ed3b4b437659bfe027e347010a87c75bcea87292-CalDigit-TS4-combind-050322-secure-Cus-22.cab",
|
||||
"emulation-url": "https://fwupd.org/downloads/e755191ff3c7d26777273262eff0be266758ebe5b79685cd44d14d491ab6107a-CalDigit-TS4-combind-050322-secure-Cus-22.zip",
|
||||
"components": [
|
||||
{
|
||||
"version": "F907.14.10",
|
||||
@ -15,6 +16,7 @@
|
||||
},
|
||||
{
|
||||
"url": "https://fwupd.org/downloads/161f0243e4f1222114b941b7dce057b962e38f9f9a1e799437531afcd5a68677-CalDigit-TS4-combind-121022-secure-Cus-37.cab",
|
||||
"emulation-url": "https://fwupd.org/downloads/21c81548a6126f806966497613bc6e33ddd5b3dc045e7cc83b27abd64ba6afd8-CalDigit-TS4-combind-121022-secure-Cus-37.zip",
|
||||
"components": [
|
||||
{
|
||||
"version": "F907.14.13",
|
||||
|
@ -4,6 +4,7 @@
|
||||
"steps": [
|
||||
{
|
||||
"url": "https://fwupd.org/downloads/f5bbeaba1037dce31dd12f349e8148ae35f98b61-a3bu-xplained123.cab",
|
||||
"emulation-url": "https://fwupd.org/downloads/01b95b0206f1a42a2bf95a432d162ef1f9f1f71edb5696127c923ceffadfdf68-a3bu-xplained123.zip",
|
||||
"components": [
|
||||
{
|
||||
"version": "1.23",
|
||||
@ -15,6 +16,7 @@
|
||||
},
|
||||
{
|
||||
"url": "https://fwupd.org/downloads/24d838541efe0340bf67e1cc5a9b95526e4d3702-a3bu-xplained124.cab",
|
||||
"emulation-url": "https://fwupd.org/downloads/357483755bbc4f3a33c0b5bc05a0ef49654be0a15c14cbde2bdf0cd9c203d632-a3bu-xplained124.zip",
|
||||
"components": [
|
||||
{
|
||||
"version": "1.24",
|
||||
|
@ -4,6 +4,7 @@
|
||||
"steps": [
|
||||
{
|
||||
"url": "https://fwupd.org/downloads/b6bef375597e848971f230cf992c9740f7bf5b92-at90usbkey123.cab",
|
||||
"emulation-url": "https://fwupd.org/downloads/57c8acd0de45ff01d91da5ecec1f826fbb438cc3f7b11ca732a8fbfdc2ce24e1-at90usbkey123.zip",
|
||||
"components": [
|
||||
{
|
||||
"version": "1.23",
|
||||
@ -15,6 +16,7 @@
|
||||
},
|
||||
{
|
||||
"url": "https://fwupd.org/downloads/47807fd4a94a4d5514ac6bf7a73038e00ed63225-at90usbkey124.cab",
|
||||
"emulation-url": "https://fwupd.org/downloads/e65432a2dd63c3ce666cc13a0f5ae13eb6d15cebbc1d022cafcff46b892cdcc4-at90usbkey124.zip",
|
||||
"components": [
|
||||
{
|
||||
"version": "1.24",
|
||||
|
@ -4,6 +4,7 @@
|
||||
"steps": [
|
||||
{
|
||||
"url": "https://fwupd.org/downloads/3c3123b6eaa89d5469553b301210a9e4cb06efa98a1cb7c6847a1d10bb0a0c4b-servo_micro_v2.4.0.cab",
|
||||
"emulation-url": "https://fwupd.org/downloads/72f84e212c5c5976bd46ea0fa4f1dda26b4c41f35ac01aaa212867e672690726-servo_micro_v2.4.0.zip",
|
||||
"components": [
|
||||
{
|
||||
"version": "2.4.0",
|
||||
@ -15,6 +16,7 @@
|
||||
},
|
||||
{
|
||||
"url": "https://fwupd.org/downloads/1dc362734f138e71fa838ac5503491d297715d7e9bccc0424a62c4a5c68526cc-servo_micro_v2.4.17.cab",
|
||||
"emulation-url": "https://fwupd.org/downloads/b5f152b8e1f814375ae586bdac9536b70b3a9cd1a2a12bf685e8da50eef78f2a-servo_micro_v2.4.17.zip",
|
||||
"components": [
|
||||
{
|
||||
"version": "2.4.17",
|
||||
|
@ -4,6 +4,7 @@
|
||||
"steps": [
|
||||
{
|
||||
"url": "https://fwupd.org/downloads/5cbff92158331aeb10008ca36fa918a9637dde7bfe31de3e0523d14090be8977-fakedevice01_dfu.cab",
|
||||
"emulation-url": "https://fwupd.org/downloads/8be5c7f3fe5c399a8699768da3f4ddd2582df19c70e197b852efaa027f42688b-fakedevice01_dfu.zip",
|
||||
"components": [
|
||||
{
|
||||
"version": "0.1",
|
||||
@ -16,6 +17,7 @@
|
||||
},
|
||||
{
|
||||
"url": "https://fwupd.org/downloads/8bc3afd07a0af3baaab8b19893791dd3972e8305-fakedevice02_dfu.cab",
|
||||
"emulation-url": "https://fwupd.org/downloads/72c580400020f22929510e9fec43d6781d56af697e72e8d560b45daa57e1817c-fakedevice02_dfu.zip",
|
||||
"components": [
|
||||
{
|
||||
"version": "0.2",
|
||||
|
@ -4,6 +4,7 @@
|
||||
"steps": [
|
||||
{
|
||||
"url": "https://fwupd.org/downloads/9a4e77009da7d3b5f15a1388afeb9e5d41a5a8ae-hughski-colorhug2-1.2.5.cab",
|
||||
"emulation-url": "https://fwupd.org/downloads/08ddc01a50fb866398d01cc4829531640e651f0233306e10a54117f88474b153-hughski-colorhug-1.2.5.zip",
|
||||
"components": [
|
||||
{
|
||||
"version": "1.2.5",
|
||||
@ -15,6 +16,7 @@
|
||||
},
|
||||
{
|
||||
"url": "https://fwupd.org/downloads/2a066c8a1bfbd99f161c867b4dbe7e51ac36fc2b16ef37b11d18419874fbcb6c-hughski-colorhug-1.2.6.cab",
|
||||
"emulation-url": "https://fwupd.org/downloads/b25122f17912467c1488aaadb59483efc3fe773cb7d002f64289177ca2f3285c-hughski-colorhug-1.2.6.zip",
|
||||
"components": [
|
||||
{
|
||||
"version": "1.2.6",
|
||||
|
@ -4,6 +4,7 @@
|
||||
"steps": [
|
||||
{
|
||||
"url": "https://fwupd.org/downloads/170f2c19f17b7819644d3fcc7617621cc3350a04-hughski-colorhug2-2.0.6.cab",
|
||||
"emulation-url": "https://fwupd.org/downloads/5ddb5d8e9ac85ea05b8dafcad638befdf3e70e5e1db0ca47489fa43d93f33a57-hughski-colorhug2-2.0.6.zip",
|
||||
"components": [
|
||||
{
|
||||
"version": "2.0.6",
|
||||
@ -15,6 +16,7 @@
|
||||
},
|
||||
{
|
||||
"url": "https://fwupd.org/downloads/e5ad222bdbd3d3d48d8613e67c7e0a0e194f8cd828e33c554d9f05d933e482c7-hughski-colorhug2-2.0.7.cab",
|
||||
"emulation-url": "https://fwupd.org/downloads/29ee025e65b6a469556bbdd819cba665b043244c90c65b9f659933a347f8cf2e-hughski-colorhug2-2.0.7.zip",
|
||||
"components": [
|
||||
{
|
||||
"version": "2.0.7",
|
||||
|
@ -5,6 +5,7 @@
|
||||
"steps": [
|
||||
{
|
||||
"url": "https://fwupd.org/downloads/2e0bf8aaf9c63ca11cfe3444d032277c21ec0d678e5963123a8b33e5dcd37d99-Lenovo-ThinkPad-USBCGen2Dock-Firmware-49-0E-14.cab",
|
||||
"emulation-url": "https://fwupd.org/downloads/9b6a1401bbd5ab3304a50a00dfc6d17d853bfbfd63f80c0360774d0e9e46e772-Lenovo-ThinkPad-USBCGen2Dock-Firmware-49-0E-14.zip",
|
||||
"components": [
|
||||
{
|
||||
"name": "cxaudio",
|
||||
|
@ -4,6 +4,7 @@
|
||||
"steps": [
|
||||
{
|
||||
"url": "https://fwupd.org/downloads/2004603b40bd529d85c2fcfcf9d50d77b47229e6564ef0ea9c03633bdccff94d-Lenovo-Travel_Hub_1in3_New.cab",
|
||||
"emulation-url": "https://fwupd.org/downloads/de48f2281354c4479b8b2302cf85b9643b0ca248fcf4beb19a71f5aba7927278-Lenovo-Travel_Hub_1in3_New.zip",
|
||||
"components": [
|
||||
{
|
||||
"name": "tier1",
|
||||
@ -16,10 +17,11 @@
|
||||
},
|
||||
{
|
||||
"url": "https://fwupd.org/downloads/5fb4f4dce233626558806c2b7474d3b5ed4f500f6013aa3b376a54322642d449-Lenovo-Travel_Hub_1in3_Old.cab",
|
||||
"emulation-url": "https://fwupd.org/downloads/58221c63de8cdec9b4db3cc72b1e0303b6cfee80b7e7965edad9816c124b0cd7-Lenovo-Travel_Hub_1in3_Old.zip",
|
||||
"components": [
|
||||
{
|
||||
"name": "tier1",
|
||||
"version": "04.33",
|
||||
"version": "4.33",
|
||||
"guids": [
|
||||
"7636b85e-d79f-5d30-a329-458957958b88"
|
||||
]
|
||||
|
@ -4,6 +4,7 @@
|
||||
"steps": [
|
||||
{
|
||||
"url": "https://fwupd.org/downloads/460a18d93f5d6118908d8437009ebbd6eceb3c6a0cdfbaf31f8a96df008564da-Realtek-RTS5423-1.56.cab",
|
||||
"emulation-url": "https://fwupd.org/downloads/eaa82f84d31f524f48369c9b46698ff00bf6e398dbb66fb7fed3e6808c69789c-Realtek-RTS5423-1.56.zip",
|
||||
"components": [
|
||||
{
|
||||
"version": "1.56",
|
||||
@ -15,6 +16,7 @@
|
||||
},
|
||||
{
|
||||
"url": "https://fwupd.org/downloads/659e721b0efbe2dfc003d3d30ea5b771c00113cc9a162333ee5a8918c4515f69-Realtek-RTS5423-1.57.cab",
|
||||
"emulation-url": "https://fwupd.org/downloads/962e175bf7809183620ecfad90583e912fb0e16a98c44b0ab539468fd340fc8a-Realtek-RTS5423-1.57.zip",
|
||||
"components": [
|
||||
{
|
||||
"version": "1.57",
|
||||
|
@ -4,6 +4,7 @@
|
||||
"steps": [
|
||||
{
|
||||
"url": "https://fwupd.org/downloads/ff989c4b71c92a4a217dfb2f82c1c87691b8eb31-rts5855_v0.4.cab",
|
||||
"emulation-url": "https://fwupd.org/downloads/75d6c26e3842bbd00991a585078e872cae66c7a81e15c5736fc478ba5ec0f77d-rts5855_v0.4.zip",
|
||||
"components": [
|
||||
{
|
||||
"version": "0.4",
|
||||
|
@ -4,6 +4,7 @@
|
||||
"steps": [
|
||||
{
|
||||
"url": "https://fwupd.org/downloads/7c260a13ea6df444f7a1fa8fa2bf431876a8c7203b6aeac371564aad30756ff1-Synaptics-Prometheus-10.01.3121519.cab",
|
||||
"emulation-url": "https://fwupd.org/downloads/73877ed7c8c15dac6adf04db32bd5e9f1e702229cfdb266fe98855ee53929cd1-Synaptics-Prometheus-10.01.3121519.zip",
|
||||
"components": [
|
||||
{
|
||||
"version": "10.01.3121519",
|
||||
|
@ -3,7 +3,7 @@
|
||||
"interactive": false,
|
||||
"steps": [
|
||||
{
|
||||
"url": "https://fwupd.org/downloads/53cd390673287a99e5d4cc4cf30b5fade0f5e708fe0b1bd424bf36467642d76c-Ugreen-CM260-7.2.1.0.cab",
|
||||
"url": "https://fwupd.org/downloads/de152591660080ed5de8550b1b2dfcd738d1f5a68bd30e5f4c684b39b9ebeeb2-Ugreen-CM260-7.2.1.0.cab",
|
||||
"components": [
|
||||
{
|
||||
"version": "7.2.1.0",
|
||||
@ -14,10 +14,10 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"url": "https://fwupd.org/downloads/eec2c6216fa86077504587633fc8d018a366a1cfe3df8dd9c575c2e8ef0ef423-Ugreen-CM260-7.2.2.0.cab",
|
||||
"url": "https://fwupd.org/downloads/9480d06d1a3e524609660653fadc337d14d72f362bd2e80b15c5c3c1733f629b-Ugreen-CM260-7.2.2.0.cab",
|
||||
"components": [
|
||||
{
|
||||
"version": "7.2.1.0",
|
||||
"version": "7.2.2.0",
|
||||
"guids": [
|
||||
"7afc5bff-be55-5e95-81ca-584f13207b1d"
|
||||
]
|
||||
|
@ -4,6 +4,7 @@
|
||||
"steps": [
|
||||
{
|
||||
"url": "https://fwupd.org/downloads/d16b682de56c42b134f1e5af7f9926c62dc246df851648e49aa1d1d0e5b38532-Wacom-Intuos_BT-M_MainFW-1.66.cab",
|
||||
"emulation-url": "https://fwupd.org/downloads/3cfdf76725fe4f58a2f0b2e41e402e6e76af37cde526f8be72b3382b230da9e1-Wacom-Intuos_BT-M_MainFW-1.66.zip",
|
||||
"components": [
|
||||
{
|
||||
"name": "main",
|
||||
@ -16,6 +17,7 @@
|
||||
},
|
||||
{
|
||||
"url": "https://fwupd.org/downloads/1ee4f3dc9fd08acd4c6bc833b25e7061f85ddd40122148d66940cd3ddd748920-Wacom-Intuos_BT-M_BluetoothFW-1.12.cab",
|
||||
"emulation-url": "https://fwupd.org/downloads/2579550908be1d3e81f0776f2b26e68f6b95e485e58c35f8b02ffdb5dec1ee13-Wacom-Intuos_BT-M_BluetoothFW-1.12.zip",
|
||||
"components": [
|
||||
{
|
||||
"name": "bluetooth",
|
||||
|
@ -4,6 +4,7 @@
|
||||
"steps": [
|
||||
{
|
||||
"url": "https://fwupd.org/downloads/f00838f10d6faf58ff57cbfcfca390ed63bd804e4c654f19ff89423b286dc5d2-lda_viking_r_composite.cab",
|
||||
"emulation-url": "https://fwupd.org/downloads/08c477c340e51990fc5df84907149f513aef65ae7f828b469e0a14ac01579a34-lda_viking_r_composite.zip",
|
||||
"components": [
|
||||
{
|
||||
"version": "1.0.1.4",
|
||||
@ -15,6 +16,7 @@
|
||||
},
|
||||
{
|
||||
"url": "https://fwupd.org/downloads/43bcfb3278ad2baf918af6abc972e0de9a0de66a25b012177b00d96664819010-lda_viking_r_composite.cab",
|
||||
"emulation-url": "https://fwupd.org/downloads/330411c3eaaba8b7e8b51108fddc614963af5b5fcc0fd328683197491584f387-lda_viking_r_composite2.zip",
|
||||
"components": [
|
||||
{
|
||||
"version": "1.0.1.6",
|
||||
|
@ -67,6 +67,10 @@ complete -c fwupdmgr -n '__fish_use_subcommand' -x -a verify-update -d 'Update t
|
||||
complete -c fwupdmgr -n '__fish_use_subcommand' -x -a inhibit -d 'Inhibit the system to prevent upgrades'
|
||||
complete -c fwupdmgr -n '__fish_use_subcommand' -x -a uninhibit -d 'Uninhibit the system to allow upgrades'
|
||||
complete -c fwupdmgr -n '__fish_use_subcommand' -x -a quit -d 'Asks the daemon to quit'
|
||||
complete -c fwupdmgr -n '__fish_use_subcommand' -x -a emulation-load -d 'Load device emulation data'
|
||||
complete -c fwupdmgr -n '__fish_use_subcommand' -x -a emulation-save -d 'Save device emulation data'
|
||||
complete -c fwupdmgr -n '__fish_use_subcommand' -x -a emulation-tag -d 'Adds devices to watch for future emulation'
|
||||
complete -c fwupdmgr -n '__fish_use_subcommand' -x -a emulation-untag -d 'Removes devices to watch for future emulation'
|
||||
|
||||
# commands exclusively consuming device IDs
|
||||
set -l deviceid_consumers activate clear-results downgrade get-releases get-results get-updates reinstall switch-branch unlock update verify verify-update
|
||||
|
@ -1,12 +1,11 @@
|
||||
#!/bin/sh
|
||||
|
||||
exec 2>&1
|
||||
dirname=`dirname $0`
|
||||
|
||||
run_test()
|
||||
{
|
||||
if [ -f $dirname/$1 ]; then
|
||||
$dirname/$1
|
||||
if [ -f @installedtestsbindir@/$1 ]; then
|
||||
@installedtestsbindir@/$1
|
||||
rc=$?; if [ $rc != 0 ]; then exit $rc; fi
|
||||
fi
|
||||
}
|
||||
@ -27,5 +26,13 @@ run_test synaptics-prometheus-self-test
|
||||
run_test dfu-self-test
|
||||
run_test mtd-self-test
|
||||
|
||||
# grab device tests from the CDN to avoid incrementing the download counter
|
||||
export FWUPD_DEVICE_TESTS_BASE_URI=http://cdn.fwupd.org/downloads
|
||||
for f in `grep --files-with-matches -r emulation-url @devicetestdir@`; do
|
||||
echo "Emulating for $f"
|
||||
fwupdmgr device-emulate --no-unreported-check --no-remote-check --no-metadata-check "$f"
|
||||
rc=$?; if [ $rc != 0 ]; then exit $rc; fi
|
||||
done
|
||||
|
||||
# success!
|
||||
exit 0
|
||||
|
@ -2,6 +2,7 @@ con2 = configuration_data()
|
||||
con2.set('installedtestsdir', installed_test_datadir)
|
||||
con2.set('installedtestsbindir', installed_test_bindir)
|
||||
con2.set('installedtestsdatadir', installed_test_datadir)
|
||||
con2.set('devicetestdir', join_paths(datadir, 'fwupd', 'device-tests'))
|
||||
con2.set('bindir', bindir)
|
||||
con2.set('libexecdir', libexecdir)
|
||||
|
||||
@ -44,9 +45,11 @@ install_data([
|
||||
install_dir: installed_test_datadir,
|
||||
)
|
||||
|
||||
install_data([
|
||||
'fwupd.sh',
|
||||
],
|
||||
configure_file(
|
||||
input: 'fwupd.sh',
|
||||
output: 'fwupd.sh',
|
||||
configuration: con2,
|
||||
install: true,
|
||||
install_dir: installed_test_bindir,
|
||||
)
|
||||
|
||||
|
1
debian/fwupd-tests.postinst
vendored
1
debian/fwupd-tests.postinst
vendored
@ -8,6 +8,7 @@ if [ "$1" = configure ] && [ -z "$2" ]; then
|
||||
if [ -f /etc/fwupd/daemon.conf ]; then
|
||||
if [ "$CI" = "true" ]; then
|
||||
sed "s,^DisabledPlugins=.*,DisabledPlugins=," -i /etc/fwupd/daemon.conf
|
||||
sed "s,^AllowEmulation=false,AllowEmulation=true," -i /etc/fwupd/daemon.conf
|
||||
else
|
||||
echo "To enable test suite, modify /etc/fwupd/daemon.conf"
|
||||
fi
|
||||
|
1
debian/fwupd-tests.postrm
vendored
1
debian/fwupd-tests.postrm
vendored
@ -7,6 +7,7 @@ if [ "$1" = remove -o "$1" = purge ]; then
|
||||
if [ -f /etc/fwupd/daemon.conf ]; then
|
||||
if [ "$CI" = "true" ]; then
|
||||
sed "s,^DisabledPlugins=,DisabledPlugins=test," -i /etc/fwupd/daemon.conf
|
||||
sed "s,^AllowEmulation=true,AllowEmulation=false," -i /etc/fwupd/daemon.conf
|
||||
else
|
||||
echo "To disable test suite, modify /etc/fwupd/daemon.conf"
|
||||
fi
|
||||
|
1
debian/tests/ci
vendored
1
debian/tests/ci
vendored
@ -4,6 +4,7 @@ set -e
|
||||
modprobe mtdram 2>&1 || true
|
||||
sed "s,^DisabledPlugins=.*,DisabledPlugins=," -i /etc/fwupd/daemon.conf
|
||||
sed "s,^VerboseDomains=.*,VerboseDomains=*," -i /etc/fwupd/daemon.conf
|
||||
sed "s,^AllowEmulation=false,AllowEmulation=true," -i /etc/fwupd/daemon.conf
|
||||
sed "s,ConditionVirtualization=.*,," \
|
||||
/lib/systemd/system/fwupd.service > \
|
||||
/etc/systemd/system/fwupd.service
|
||||
|
71
docs/device-emulation.md
Normal file
71
docs/device-emulation.md
Normal file
@ -0,0 +1,71 @@
|
||||
---
|
||||
title: Device Emulation
|
||||
---
|
||||
|
||||
## Introduction
|
||||
|
||||
Using device-tests, fwupd can prevent regressions by updating and downgrading firmware on real
|
||||
hardware. However, much past a few dozen devices this does not scale, either by time, or because
|
||||
real devices need plugging in and out. We can unit test the plugin internals, but this does not
|
||||
actually test devices being attached, removed, and being updated.
|
||||
|
||||
By recording the backend devices we can build a "history" in GUsb of what control, interrupt and
|
||||
bulk transfers were sent to, and received from the device. By dumping these we can "replay" the
|
||||
update without the physical hardware connected.
|
||||
|
||||
There are some problems that make this emulation slightly harder than the naive implementation:
|
||||
|
||||
* Devices are sometimes detached into a different "bootloader" device with a new VID:PID
|
||||
* Devices might be "composite" and actually be multiple logical devices in one physical device
|
||||
* Devices might not be 100% deterimistic, e.g. queries might be processed out-of-order
|
||||
|
||||
For people to generate and consume emulated devices, we do need to make the process easy to
|
||||
understand, and also easy to use. Some key points that we think are important, is the ability to:
|
||||
|
||||
* Dump the device of an unmodified running daemon.
|
||||
* Filter to multiple or single devices, to avoid storing data for unrelated parts of the system.
|
||||
* Load an emulated device into an unmodified running daemon.
|
||||
|
||||
Because we do not want to modify the daemon, we think it makes sense to *load* and *save* emulation
|
||||
state over D-Bus. Each phase can be controlled, which makes it easy to view, and edit, the recorded
|
||||
emulation data.
|
||||
|
||||
For instance, calling `fwupdmgr emulation-tag` would ask the end user to choose a device to start
|
||||
*recording* so that subsequent re-plugs are available to save.
|
||||
As the device state may not be persistent we save the device-should-be-recorded metadata in the
|
||||
pending database like we would do for a successful firmware update.
|
||||
|
||||
To demo this, something like this could be done:
|
||||
|
||||
# connect ColorHug2
|
||||
fwupdmgr modify-config AllowEmulation true
|
||||
fwupdmgr emulation-tag b0a78eb71f4eeea7df8fb114522556ba8ce22074
|
||||
# or, using the GUID
|
||||
# fwupdmgr emulation-tag 2082b5e0-7a64-478a-b1b2-e3404fab6dad
|
||||
# remove and re-insert ColorHug2
|
||||
fwupdmgr get-devices --filter emulation-tag
|
||||
fwupdmgr download https://fwupd.org/downloads/170f2c19f17b7819644d3fcc7617621cc3350a04-hughski-colorhug2-2.0.6.cab
|
||||
fwupdmgr install e5* --allow-reinstall
|
||||
fwupdmgr emulation-save colorhug.zip
|
||||
# remove ColorHug2
|
||||
fwupdmgr emulation-load colorhug.zip
|
||||
fwupdmgr get-devices --filter emulated
|
||||
fwupdmgr install e5* --allow-reinstall
|
||||
fwupdmgr modify-config AllowEmulation false
|
||||
|
||||
## Device Tests
|
||||
|
||||
The `emulation-url` string parameter can be specified in the `steps` section of a specific device
|
||||
test. This causes the front end to load the emulation data before running the specific step.
|
||||
|
||||
Device tests without emulation data will be skipped.
|
||||
|
||||
For example:
|
||||
|
||||
fwupdmgr device-emulate ../data/device-tests/hughski-colorhug2.json
|
||||
Decompressing… [***************************************]
|
||||
Waiting… [***************************************]
|
||||
Hughski ColorHug2: OK!
|
||||
Decompressing… [***************************************]
|
||||
Waiting… [***************************************]
|
||||
Hughski ColorHug2: OK!
|
@ -48,6 +48,9 @@
|
||||
<ul>
|
||||
<li><a href="libfwupdplugin/hwids.html">Hardware IDs</a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li><a href="libfwupdplugin/device-emulation.html">Device Emulation</a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li><a href="libfwupdplugin/env.html">Environment variables</a></li>
|
||||
</ul>
|
||||
|
@ -2566,3 +2566,113 @@ fwupd_client_upload_bytes(FwupdClient *self,
|
||||
}
|
||||
return g_steal_pointer(&helper->bytes);
|
||||
}
|
||||
|
||||
static void
|
||||
fwupd_client_emulation_load_cb(GObject *source, GAsyncResult *res, gpointer user_data)
|
||||
{
|
||||
FwupdClientHelper *helper = (FwupdClientHelper *)user_data;
|
||||
helper->ret = fwupd_client_emulation_load_finish(FWUPD_CLIENT(source), res, &helper->error);
|
||||
g_main_loop_quit(helper->loop);
|
||||
}
|
||||
|
||||
/**
|
||||
* fwupd_client_emulation_load
|
||||
* @self: a #FwupdClient
|
||||
* @data: archive data of JSON files
|
||||
* @cancellable: (nullable): optional #GCancellable
|
||||
* @error: (nullable): optional return location for an error
|
||||
*
|
||||
* Loads an emulated device into the daemon backend that has the phases set by the JSON data,
|
||||
* for instance, having one USB device emulated for the bootloader and another emulated for the
|
||||
* runtime interface.
|
||||
*
|
||||
* Returns: %TRUE for success
|
||||
*
|
||||
* Since: 1.8.11
|
||||
**/
|
||||
gboolean
|
||||
fwupd_client_emulation_load(FwupdClient *self,
|
||||
GBytes *data,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
g_autoptr(FwupdClientHelper) helper = NULL;
|
||||
|
||||
g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE);
|
||||
g_return_val_if_fail(data != NULL, FALSE);
|
||||
g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), FALSE);
|
||||
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
|
||||
|
||||
/* connect */
|
||||
if (!fwupd_client_connect(self, cancellable, error))
|
||||
return FALSE;
|
||||
|
||||
/* call async version and run loop until complete */
|
||||
helper = fwupd_client_helper_new(self);
|
||||
fwupd_client_emulation_load_async(self,
|
||||
data,
|
||||
cancellable,
|
||||
fwupd_client_emulation_load_cb,
|
||||
helper);
|
||||
g_main_loop_run(helper->loop);
|
||||
if (!helper->ret) {
|
||||
g_propagate_error(error, g_steal_pointer(&helper->error));
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
fwupd_client_emulation_save_cb(GObject *source, GAsyncResult *res, gpointer user_data)
|
||||
{
|
||||
FwupdClientHelper *helper = (FwupdClientHelper *)user_data;
|
||||
helper->bytes =
|
||||
fwupd_client_emulation_save_finish(FWUPD_CLIENT(source), res, &helper->error);
|
||||
g_main_loop_quit(helper->loop);
|
||||
}
|
||||
|
||||
/**
|
||||
* fwupd_client_emulation_save:
|
||||
* @self: a #FwupdClient
|
||||
* @cancellable: (nullable): optional #GCancellable
|
||||
* @error: (nullable): optional return location for an error
|
||||
*
|
||||
* Gets the captured data from all filtered devices for all recorded phases. The data is returned
|
||||
* in a ZIP archive of JSON output.
|
||||
*
|
||||
* NOTE: Device events are not automatically recorded for all devices. You must call something
|
||||
* like `ModifyDevice(device_id, 'flags','emulation-tag')` to start the recording the backend.
|
||||
*
|
||||
* Once the device has been re-inserted then the emulation data will be available using
|
||||
* this API call.
|
||||
*
|
||||
* Returns: (transfer full): archive data
|
||||
*
|
||||
* Since: 1.8.11
|
||||
**/
|
||||
GBytes *
|
||||
fwupd_client_emulation_save(FwupdClient *self, GCancellable *cancellable, GError **error)
|
||||
{
|
||||
g_autoptr(FwupdClientHelper) helper = NULL;
|
||||
|
||||
g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL);
|
||||
g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), NULL);
|
||||
g_return_val_if_fail(error == NULL || *error == NULL, NULL);
|
||||
|
||||
/* connect */
|
||||
if (!fwupd_client_connect(self, cancellable, error))
|
||||
return NULL;
|
||||
|
||||
/* call async version and run loop until complete */
|
||||
helper = fwupd_client_helper_new(self);
|
||||
fwupd_client_emulation_save_async(self,
|
||||
cancellable,
|
||||
fwupd_client_emulation_save_cb,
|
||||
helper);
|
||||
g_main_loop_run(helper->loop);
|
||||
if (helper->bytes == NULL) {
|
||||
g_propagate_error(error, g_steal_pointer(&helper->error));
|
||||
return NULL;
|
||||
}
|
||||
return g_steal_pointer(&helper->bytes);
|
||||
}
|
||||
|
@ -254,5 +254,14 @@ fwupd_client_upload_bytes(FwupdClient *self,
|
||||
FwupdClientUploadFlags flags,
|
||||
GCancellable *cancellable,
|
||||
GError **error) G_GNUC_WARN_UNUSED_RESULT;
|
||||
gboolean
|
||||
fwupd_client_emulation_load(FwupdClient *self,
|
||||
GBytes *data,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
GBytes *
|
||||
fwupd_client_emulation_save(FwupdClient *self,
|
||||
GCancellable *cancellable,
|
||||
GError **error) G_GNUC_WARN_UNUSED_RESULT;
|
||||
|
||||
G_END_DECLS
|
||||
|
@ -5650,6 +5650,175 @@ fwupd_client_add_hint(FwupdClient *self, const gchar *key, const gchar *value)
|
||||
g_hash_table_insert(priv->hints, g_strdup(key), g_strdup(value));
|
||||
}
|
||||
|
||||
static void
|
||||
fwupd_client_emulation_load_cb(GObject *source, GAsyncResult *res, gpointer user_data)
|
||||
{
|
||||
g_autoptr(GTask) task = G_TASK(user_data);
|
||||
g_autoptr(GError) error = NULL;
|
||||
g_autoptr(GVariant) val = NULL;
|
||||
|
||||
val = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error);
|
||||
if (val == NULL) {
|
||||
fwupd_client_fixup_dbus_error(error);
|
||||
g_task_return_error(task, g_steal_pointer(&error));
|
||||
return;
|
||||
}
|
||||
|
||||
/* success */
|
||||
g_task_return_boolean(task, TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* fwupd_client_emulation_load_async:
|
||||
* @self: a #FwupdClient
|
||||
* @data: archive data of JSON files
|
||||
* @cancellable: (nullable): optional #GCancellable
|
||||
* @callback: the function to run on completion
|
||||
* @callback_data: the data to pass to @callback
|
||||
*
|
||||
* Loads an emulated device into the daemon backend that has the phases set by the JSON data,
|
||||
* for instance, having one USB device emulated for the bootloader and another emulated for the
|
||||
* runtime interface.
|
||||
*
|
||||
* Since: 1.8.11
|
||||
**/
|
||||
void
|
||||
fwupd_client_emulation_load_async(FwupdClient *self,
|
||||
GBytes *data,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer callback_data)
|
||||
{
|
||||
FwupdClientPrivate *priv = GET_PRIVATE(self);
|
||||
g_autoptr(GTask) task = NULL;
|
||||
GVariant *variant;
|
||||
|
||||
g_return_if_fail(FWUPD_IS_CLIENT(self));
|
||||
g_return_if_fail(data != NULL);
|
||||
g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable));
|
||||
g_return_if_fail(priv->proxy != NULL);
|
||||
|
||||
/* call into daemon */
|
||||
task = g_task_new(self, cancellable, callback, callback_data);
|
||||
variant = g_variant_new_from_bytes(G_VARIANT_TYPE_BYTESTRING, data, FALSE);
|
||||
g_dbus_proxy_call(priv->proxy,
|
||||
"EmulationLoad",
|
||||
g_variant_new_tuple(&variant, 1),
|
||||
G_DBUS_CALL_FLAGS_NONE,
|
||||
FWUPD_CLIENT_DBUS_PROXY_TIMEOUT,
|
||||
cancellable,
|
||||
fwupd_client_emulation_load_cb,
|
||||
g_steal_pointer(&task));
|
||||
}
|
||||
|
||||
/**
|
||||
* fwupd_client_emulation_load_finish:
|
||||
* @self: a #FwupdClient
|
||||
* @res: (not nullable): the asynchronous result
|
||||
* @error: (nullable): optional return location for an error
|
||||
*
|
||||
* Gets the result of [method@FwupdClient.emulation_load_async].
|
||||
*
|
||||
* Returns: %TRUE for success
|
||||
*
|
||||
* Since: 1.8.11
|
||||
**/
|
||||
gboolean
|
||||
fwupd_client_emulation_load_finish(FwupdClient *self, GAsyncResult *res, GError **error)
|
||||
{
|
||||
g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE);
|
||||
g_return_val_if_fail(g_task_is_valid(res, self), FALSE);
|
||||
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
|
||||
return g_task_propagate_boolean(G_TASK(res), error);
|
||||
}
|
||||
|
||||
static void
|
||||
fwupd_client_emulation_save_cb(GObject *source, GAsyncResult *res, gpointer user_data)
|
||||
{
|
||||
g_autoptr(GTask) task = G_TASK(user_data);
|
||||
g_autoptr(GError) error = NULL;
|
||||
g_autoptr(GVariant) val = NULL;
|
||||
|
||||
val = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error);
|
||||
if (val == NULL) {
|
||||
fwupd_client_fixup_dbus_error(error);
|
||||
g_task_return_error(task, g_steal_pointer(&error));
|
||||
return;
|
||||
}
|
||||
|
||||
/* success */
|
||||
g_task_return_pointer(task,
|
||||
g_variant_get_data_as_bytes(val),
|
||||
(GDestroyNotify)g_bytes_unref);
|
||||
}
|
||||
|
||||
/**
|
||||
* fwupd_client_emulation_save_async:
|
||||
* @self: a #FwupdClient
|
||||
* @cancellable: (nullable): optional #GCancellable
|
||||
* @callback: the function to run on completion
|
||||
* @callback_data: the data to pass to @callback
|
||||
*
|
||||
* Gets the captured data from all filtered devices for all recorded phases. The data is returned
|
||||
* in a ZIP archive of JSON output.
|
||||
*
|
||||
* NOTE: Device events are not automatically recorded for all devices. You must call something
|
||||
* like `ModifyDevice(device_id, 'flags','emulation-tag')` to start the recording the backend.
|
||||
*
|
||||
* Once the device has been re-inserted then the emulation data will be available using
|
||||
* this API call.
|
||||
*
|
||||
* You must have called [method@Client.connect_async] on @self before using
|
||||
* this method.
|
||||
*
|
||||
* Since: 1.8.11
|
||||
**/
|
||||
void
|
||||
fwupd_client_emulation_save_async(FwupdClient *self,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer callback_data)
|
||||
{
|
||||
FwupdClientPrivate *priv = GET_PRIVATE(self);
|
||||
g_autoptr(GTask) task = NULL;
|
||||
|
||||
g_return_if_fail(FWUPD_IS_CLIENT(self));
|
||||
g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable));
|
||||
g_return_if_fail(priv->proxy != NULL);
|
||||
|
||||
/* call into daemon */
|
||||
task = g_task_new(self, cancellable, callback, callback_data);
|
||||
g_dbus_proxy_call(priv->proxy,
|
||||
"EmulationSave",
|
||||
NULL,
|
||||
G_DBUS_CALL_FLAGS_NONE,
|
||||
FWUPD_CLIENT_DBUS_PROXY_TIMEOUT,
|
||||
cancellable,
|
||||
fwupd_client_emulation_save_cb,
|
||||
g_steal_pointer(&task));
|
||||
}
|
||||
|
||||
/**
|
||||
* fwupd_client_emulation_save_finish:
|
||||
* @self: a #FwupdClient
|
||||
* @res: (not nullable): the asynchronous result
|
||||
* @error: (nullable): optional return location for an error
|
||||
*
|
||||
* Gets the result of [method@FwupdClient.emulation_save_async].
|
||||
*
|
||||
* Returns: (transfer full): archive data
|
||||
*
|
||||
* Since: 1.8.11
|
||||
**/
|
||||
GBytes *
|
||||
fwupd_client_emulation_save_finish(FwupdClient *self, GAsyncResult *res, GError **error)
|
||||
{
|
||||
g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL);
|
||||
g_return_val_if_fail(g_task_is_valid(res, self), NULL);
|
||||
g_return_val_if_fail(error == NULL || *error == NULL, NULL);
|
||||
return g_task_propagate_pointer(G_TASK(res), error);
|
||||
}
|
||||
|
||||
#ifdef SOUP_SESSION_COMPAT
|
||||
/* this is bad; we dlopen libsoup-2.4.so.1 and get the gtype manually
|
||||
* to avoid deps on both libcurl and libsoup whilst preserving ABI */
|
||||
|
@ -405,6 +405,23 @@ gboolean
|
||||
fwupd_client_uninhibit_finish(FwupdClient *self,
|
||||
GAsyncResult *res,
|
||||
GError **error) G_GNUC_WARN_UNUSED_RESULT;
|
||||
void
|
||||
fwupd_client_emulation_load_async(FwupdClient *self,
|
||||
GBytes *data,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer callback_data);
|
||||
gboolean
|
||||
fwupd_client_emulation_load_finish(FwupdClient *self, GAsyncResult *res, GError **error);
|
||||
void
|
||||
fwupd_client_emulation_save_async(FwupdClient *self,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer callback_data);
|
||||
GBytes *
|
||||
fwupd_client_emulation_save_finish(FwupdClient *self,
|
||||
GAsyncResult *res,
|
||||
GError **error) G_GNUC_WARN_UNUSED_RESULT;
|
||||
|
||||
FwupdStatus
|
||||
fwupd_client_get_status(FwupdClient *self);
|
||||
|
@ -211,6 +211,8 @@ fwupd_device_flag_to_string(FwupdDeviceFlags device_flag)
|
||||
return "unsigned-payload";
|
||||
if (device_flag == FWUPD_DEVICE_FLAG_EMULATED)
|
||||
return "emulated";
|
||||
if (device_flag == FWUPD_DEVICE_FLAG_EMULATION_TAG)
|
||||
return "emulation-tag";
|
||||
if (device_flag == FWUPD_DEVICE_FLAG_UNKNOWN)
|
||||
return "unknown";
|
||||
return NULL;
|
||||
@ -332,6 +334,8 @@ fwupd_device_flag_from_string(const gchar *device_flag)
|
||||
return FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD;
|
||||
if (g_strcmp0(device_flag, "emulated") == 0)
|
||||
return FWUPD_DEVICE_FLAG_EMULATED;
|
||||
if (g_strcmp0(device_flag, "emulation-tag") == 0)
|
||||
return FWUPD_DEVICE_FLAG_EMULATION_TAG;
|
||||
return FWUPD_DEVICE_FLAG_UNKNOWN;
|
||||
}
|
||||
|
||||
|
@ -535,6 +535,14 @@ typedef enum {
|
||||
* Since: 1.8.11
|
||||
*/
|
||||
#define FWUPD_DEVICE_FLAG_EMULATED (1llu << 49)
|
||||
/**
|
||||
* FWUPD_DEVICE_FLAG_EMULATION_TAG:
|
||||
*
|
||||
* The device should be recorded by the backend, allowing emulation.
|
||||
*
|
||||
* Since: 1.8.11
|
||||
*/
|
||||
#define FWUPD_DEVICE_FLAG_EMULATION_TAG (1llu << 50)
|
||||
/**
|
||||
* FWUPD_DEVICE_FLAG_UNKNOWN:
|
||||
*
|
||||
|
@ -904,6 +904,12 @@ LIBFWUPD_1.8.8 {
|
||||
|
||||
LIBFWUPD_1.8.11 {
|
||||
global:
|
||||
fwupd_client_emulation_load;
|
||||
fwupd_client_emulation_load_async;
|
||||
fwupd_client_emulation_load_finish;
|
||||
fwupd_client_emulation_save;
|
||||
fwupd_client_emulation_save_async;
|
||||
fwupd_client_emulation_save_finish;
|
||||
fwupd_client_inhibit;
|
||||
fwupd_client_inhibit_async;
|
||||
fwupd_client_inhibit_finish;
|
||||
|
@ -8,5 +8,7 @@
|
||||
|
||||
#include "fu-usb-device.h"
|
||||
|
||||
#define FU_USB_DEVICE_EMULATION_TAG "org.freedesktop.fwupd.emulation.v1"
|
||||
|
||||
const gchar *
|
||||
fu_usb_device_get_platform_id(FuUsbDevice *self);
|
||||
|
@ -86,6 +86,18 @@ fu_usb_device_finalize(GObject *object)
|
||||
G_OBJECT_CLASS(fu_usb_device_parent_class)->finalize(object);
|
||||
}
|
||||
|
||||
#if G_USB_CHECK_VERSION(0, 4, 5)
|
||||
static void
|
||||
fu_usb_device_flags_notify_cb(FuDevice *device, GParamSpec *pspec, gpointer user_data)
|
||||
{
|
||||
GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device));
|
||||
if (usb_device == NULL)
|
||||
return;
|
||||
if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATION_TAG))
|
||||
g_usb_device_add_tag(usb_device, FU_USB_DEVICE_EMULATION_TAG);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void
|
||||
fu_usb_device_init(FuUsbDevice *device)
|
||||
{
|
||||
@ -104,6 +116,19 @@ fu_usb_device_init(FuUsbDevice *device)
|
||||
#endif
|
||||
}
|
||||
|
||||
static void
|
||||
fu_usb_device_constructed(GObject *obj)
|
||||
{
|
||||
FuUsbDevice *self = FU_USB_DEVICE(obj);
|
||||
#if G_USB_CHECK_VERSION(0, 4, 5)
|
||||
/* copy this to the GUsbDevice */
|
||||
g_signal_connect(FU_DEVICE(self),
|
||||
"notify::flags",
|
||||
G_CALLBACK(fu_usb_device_flags_notify_cb),
|
||||
NULL);
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* fu_usb_device_is_open:
|
||||
* @device: a #FuUsbDevice
|
||||
@ -675,7 +700,7 @@ fu_usb_device_set_dev(FuUsbDevice *device, GUsbDevice *usb_device)
|
||||
}
|
||||
|
||||
#ifdef HAVE_GUSB
|
||||
#if G_USB_CHECK_VERSION(0, 4, 4)
|
||||
#if G_USB_CHECK_VERSION(0, 4, 5)
|
||||
/* propagate emulated flag */
|
||||
if (usb_device != NULL && g_usb_device_is_emulated(usb_device))
|
||||
fu_device_add_flag(FU_DEVICE(device), FWUPD_DEVICE_FLAG_EMULATED);
|
||||
@ -913,6 +938,7 @@ fu_usb_device_class_init(FuUsbDeviceClass *klass)
|
||||
object_class->finalize = fu_usb_device_finalize;
|
||||
object_class->get_property = fu_usb_device_get_property;
|
||||
object_class->set_property = fu_usb_device_set_property;
|
||||
object_class->constructed = fu_usb_device_constructed;
|
||||
device_class->open = fu_usb_device_open;
|
||||
device_class->setup = fu_usb_device_setup;
|
||||
device_class->ready = fu_usb_device_ready;
|
||||
|
@ -220,6 +220,9 @@ fu_corsair_bp_incorporate(FuDevice *self, FuDevice *donor)
|
||||
FuCorsairBp *bp_self = FU_CORSAIR_BP(self);
|
||||
FuCorsairBp *bp_donor = FU_CORSAIR_BP(donor);
|
||||
|
||||
/* FuUsbDevice */
|
||||
FU_DEVICE_CLASS(fu_corsair_bp_parent_class)->incorporate(self, donor);
|
||||
|
||||
bp_self->epin = bp_donor->epin;
|
||||
bp_self->epout = bp_donor->epout;
|
||||
bp_self->cmd_write_size = bp_donor->cmd_write_size;
|
||||
|
@ -38,6 +38,7 @@ struct _FuConfig {
|
||||
gboolean ignore_power;
|
||||
gboolean only_trusted;
|
||||
gboolean show_device_private;
|
||||
gboolean allow_emulation;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE(FuConfig, fu_config, G_TYPE_OBJECT)
|
||||
@ -70,6 +71,7 @@ fu_config_reload(FuConfig *self, GError **error)
|
||||
g_autoptr(GError) error_ignore_power = NULL;
|
||||
g_autoptr(GError) error_only_trusted = NULL;
|
||||
g_autoptr(GError) error_show_device_private = NULL;
|
||||
g_autoptr(GError) error_allow_emulation = NULL;
|
||||
g_autoptr(GError) error_enumerate_all = NULL;
|
||||
g_autoptr(GByteArray) buf = g_byte_array_new();
|
||||
|
||||
@ -247,6 +249,14 @@ fu_config_reload(FuConfig *self, GError **error)
|
||||
self->show_device_private = TRUE;
|
||||
}
|
||||
|
||||
/* whether to allow emulation to work */
|
||||
self->allow_emulation =
|
||||
g_key_file_get_boolean(keyfile, "fwupd", "AllowEmulation", &error_allow_emulation);
|
||||
if (!self->allow_emulation && error_allow_emulation != NULL) {
|
||||
g_debug("failed to read AllowEmulation key: %s", error_allow_emulation->message);
|
||||
self->allow_emulation = FALSE;
|
||||
}
|
||||
|
||||
/* fetch host best known configuration */
|
||||
host_bkc = g_key_file_get_string(keyfile, "fwupd", "HostBkc", NULL);
|
||||
if (host_bkc != NULL && host_bkc[0] != '\0')
|
||||
@ -446,6 +456,13 @@ fu_config_get_show_device_private(FuConfig *self)
|
||||
return self->show_device_private;
|
||||
}
|
||||
|
||||
gboolean
|
||||
fu_config_get_allow_emulation(FuConfig *self)
|
||||
{
|
||||
g_return_val_if_fail(FU_IS_CONFIG(self), FALSE);
|
||||
return self->allow_emulation;
|
||||
}
|
||||
|
||||
gboolean
|
||||
fu_config_get_enumerate_all_devices(FuConfig *self)
|
||||
{
|
||||
|
@ -44,6 +44,8 @@ gboolean
|
||||
fu_config_get_only_trusted(FuConfig *self);
|
||||
gboolean
|
||||
fu_config_get_show_device_private(FuConfig *self);
|
||||
gboolean
|
||||
fu_config_get_allow_emulation(FuConfig *self);
|
||||
const gchar *
|
||||
fu_config_get_host_bkc(FuConfig *self);
|
||||
const gchar *
|
||||
|
@ -1399,6 +1399,45 @@ fu_daemon_daemon_method_call(GDBusConnection *connection,
|
||||
g_dbus_method_invocation_return_value(invocation, NULL);
|
||||
return;
|
||||
}
|
||||
if (g_strcmp0(method_name, "EmulationLoad") == 0) {
|
||||
g_autoptr(GBytes) data = NULL;
|
||||
|
||||
g_debug("Called %s()", method_name);
|
||||
|
||||
/* load data into engine */
|
||||
data = g_variant_get_data_as_bytes(g_variant_get_child_value(parameters, 0));
|
||||
if (!fu_engine_emulation_load(self->engine, data, &error)) {
|
||||
g_dbus_method_invocation_return_error(invocation,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_NOT_SUPPORTED,
|
||||
"failed to load emulation data: %s",
|
||||
error->message);
|
||||
return;
|
||||
}
|
||||
|
||||
/* success */
|
||||
g_dbus_method_invocation_return_value(invocation, NULL);
|
||||
return;
|
||||
}
|
||||
if (g_strcmp0(method_name, "EmulationSave") == 0) {
|
||||
g_autoptr(GBytes) data = NULL;
|
||||
|
||||
g_debug("Called %s()", method_name);
|
||||
|
||||
/* save data from engine */
|
||||
data = fu_engine_emulation_save(self->engine, &error);
|
||||
if (data == NULL) {
|
||||
g_dbus_method_invocation_return_error(invocation,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_NOT_SUPPORTED,
|
||||
"failed to save emulation data: %s",
|
||||
error->message);
|
||||
return;
|
||||
}
|
||||
val = g_variant_new_from_bytes(G_VARIANT_TYPE_BYTESTRING, data, FALSE);
|
||||
g_dbus_method_invocation_return_value(invocation, g_variant_new_tuple(&val, 1));
|
||||
return;
|
||||
}
|
||||
if (g_strcmp0(method_name, "ModifyDevice") == 0) {
|
||||
const gchar *device_id;
|
||||
const gchar *key = NULL;
|
||||
|
@ -688,6 +688,8 @@ fu_device_list_replace(FuDeviceList *self, FuDeviceItem *item, FuDevice *device)
|
||||
fu_device_add_flag(device, FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD);
|
||||
if (fu_device_has_flag(item->device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD))
|
||||
fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);
|
||||
if (fu_device_has_flag(item->device, FWUPD_DEVICE_FLAG_EMULATION_TAG))
|
||||
fu_device_add_flag(device, FWUPD_DEVICE_FLAG_EMULATION_TAG);
|
||||
|
||||
/* device won't come back in right mode */
|
||||
if (fu_device_has_flag(item->device, FWUPD_DEVICE_FLAG_WILL_DISAPPEAR)) {
|
||||
@ -866,7 +868,8 @@ fu_device_list_get_wait_for_replug(FuDeviceList *self)
|
||||
GPtrArray *devices = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref);
|
||||
for (guint i = 0; i < self->devices->len; i++) {
|
||||
FuDeviceItem *item_tmp = g_ptr_array_index(self->devices, i);
|
||||
if (fu_device_has_flag(item_tmp->device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG))
|
||||
if (fu_device_has_flag(item_tmp->device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG) &&
|
||||
!fu_device_has_flag(item_tmp->device, FWUPD_DEVICE_FLAG_EMULATED))
|
||||
g_ptr_array_add(devices, g_object_ref(item_tmp->device));
|
||||
}
|
||||
return devices;
|
||||
|
486
src/fu-engine.c
486
src/fu-engine.c
@ -96,7 +96,7 @@ static void
|
||||
fu_engine_ensure_security_attrs(FuEngine *self);
|
||||
|
||||
typedef enum {
|
||||
FU_ENGINE_INSTALL_PHASE_UNKNOWN,
|
||||
FU_ENGINE_INSTALL_PHASE_SETUP,
|
||||
FU_ENGINE_INSTALL_PHASE_WRITE,
|
||||
FU_ENGINE_INSTALL_PHASE_ATTACH,
|
||||
FU_ENGINE_INSTALL_PHASE_DETACH,
|
||||
@ -132,6 +132,8 @@ struct _FuEngine {
|
||||
GHashTable *compile_versions;
|
||||
GHashTable *approved_firmware; /* (nullable) */
|
||||
GHashTable *blocked_firmware; /* (nullable) */
|
||||
GHashTable *emulation_phases; /* (element-type int utf8) */
|
||||
GHashTable *emulation_backend_ids; /* (element-type str int) */
|
||||
gchar *host_machine_id;
|
||||
JcatContext *jcat_context;
|
||||
gboolean loaded;
|
||||
@ -370,6 +372,8 @@ fu_engine_device_request_cb(FuDevice *device, FwupdRequest *request, FuEngine *s
|
||||
static const gchar *
|
||||
fu_engine_install_phase_to_string(FuEngineInstallPhase phase)
|
||||
{
|
||||
if (phase == FU_ENGINE_INSTALL_PHASE_SETUP)
|
||||
return "setup";
|
||||
if (phase == FU_ENGINE_INSTALL_PHASE_WRITE)
|
||||
return "install";
|
||||
if (phase == FU_ENGINE_INSTALL_PHASE_ATTACH)
|
||||
@ -763,6 +767,7 @@ gboolean
|
||||
fu_engine_modify_config(FuEngine *self, const gchar *key, const gchar *value, GError **error)
|
||||
{
|
||||
const gchar *keys[] = {"ArchiveSizeMax",
|
||||
"AllowEmulation",
|
||||
"ApprovedFirmware",
|
||||
"BlockedFirmware",
|
||||
"DisabledDevices",
|
||||
@ -1056,6 +1061,118 @@ fu_engine_modify_bios_settings(FuEngine *self,
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
fu_engine_check_context_flag_save_events(FuEngine *self)
|
||||
{
|
||||
if (g_hash_table_size(self->emulation_backend_ids) > 0 &&
|
||||
fu_config_get_allow_emulation(self->config)) {
|
||||
fu_context_add_flag(self->ctx, FU_CONTEXT_FLAG_SAVE_EVENTS);
|
||||
} else {
|
||||
fu_context_remove_flag(self->ctx, FU_CONTEXT_FLAG_SAVE_EVENTS);
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_engine_remove_device_flag(FuEngine *self,
|
||||
const gchar *device_id,
|
||||
FwupdDeviceFlags flag,
|
||||
GError **error)
|
||||
{
|
||||
FuDevice *proxy;
|
||||
g_autoptr(FuDevice) device = NULL;
|
||||
|
||||
if (flag == FWUPD_DEVICE_FLAG_NOTIFIED) {
|
||||
device = fu_history_get_device_by_id(self->history, device_id, error);
|
||||
if (device == NULL)
|
||||
return FALSE;
|
||||
fu_device_remove_flag(device, flag);
|
||||
return fu_history_modify_device(self->history, device, error);
|
||||
}
|
||||
if (flag == FWUPD_DEVICE_FLAG_EMULATION_TAG) {
|
||||
device = fu_device_list_get_by_id(self->device_list, device_id, error);
|
||||
if (device == NULL)
|
||||
return FALSE;
|
||||
proxy = fu_device_get_proxy(device);
|
||||
if (proxy != NULL) {
|
||||
g_set_error(error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_NOT_SUPPORTED,
|
||||
"device %s uses a proxy, remove the flag on %s instead",
|
||||
fu_device_get_id(device),
|
||||
fu_device_get_id(proxy));
|
||||
return FALSE;
|
||||
}
|
||||
g_hash_table_remove(self->emulation_backend_ids, fu_device_get_backend_id(device));
|
||||
return TRUE;
|
||||
}
|
||||
g_set_error_literal(error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_NOT_SUPPORTED,
|
||||
"flag cannot be removed from client");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_engine_add_device_flag(FuEngine *self,
|
||||
const gchar *device_id,
|
||||
FwupdDeviceFlags flag,
|
||||
GError **error)
|
||||
{
|
||||
FuDevice *proxy;
|
||||
g_autoptr(FuDevice) device = NULL;
|
||||
|
||||
if (flag == FWUPD_DEVICE_FLAG_REPORTED || flag == FWUPD_DEVICE_FLAG_NOTIFIED) {
|
||||
device = fu_history_get_device_by_id(self->history, device_id, error);
|
||||
if (device == NULL)
|
||||
return FALSE;
|
||||
fu_device_add_flag(device, flag);
|
||||
return fu_history_modify_device(self->history, device, error);
|
||||
}
|
||||
if (flag == FWUPD_DEVICE_FLAG_EMULATION_TAG) {
|
||||
device = fu_device_list_get_by_id(self->device_list, device_id, error);
|
||||
if (device == NULL)
|
||||
return FALSE;
|
||||
proxy = fu_device_get_proxy(device);
|
||||
if (proxy != NULL) {
|
||||
g_set_error(error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_NOT_SUPPORTED,
|
||||
"device %s uses a proxy, set the flag on %s instead",
|
||||
fu_device_get_id(device),
|
||||
fu_device_get_id(proxy));
|
||||
return FALSE;
|
||||
}
|
||||
g_hash_table_insert(self->emulation_backend_ids,
|
||||
g_strdup(fu_device_get_backend_id(device)),
|
||||
GUINT_TO_POINTER(1));
|
||||
return TRUE;
|
||||
}
|
||||
g_set_error_literal(error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_NOT_SUPPORTED,
|
||||
"flag cannot be added from client");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_engine_modify_device_flags(FuEngine *self,
|
||||
const gchar *device_id,
|
||||
const gchar *value,
|
||||
GError **error)
|
||||
{
|
||||
/* add or remove a subset of device flags */
|
||||
if (g_str_has_prefix(value, "~")) {
|
||||
return fu_engine_remove_device_flag(self,
|
||||
device_id,
|
||||
fwupd_device_flag_from_string(value + 1),
|
||||
error);
|
||||
}
|
||||
return fu_engine_add_device_flag(self,
|
||||
device_id,
|
||||
fwupd_device_flag_from_string(value),
|
||||
error);
|
||||
}
|
||||
|
||||
/**
|
||||
* fu_engine_modify_device:
|
||||
* @self: a #FuEngine
|
||||
@ -1076,45 +1193,8 @@ fu_engine_modify_device(FuEngine *self,
|
||||
const gchar *value,
|
||||
GError **error)
|
||||
{
|
||||
g_autoptr(FuDevice) device = NULL;
|
||||
|
||||
/* find the correct device */
|
||||
device = fu_history_get_device_by_id(self->history, device_id, error);
|
||||
if (device == NULL)
|
||||
return FALSE;
|
||||
|
||||
/* support adding and removing a subset of device flags */
|
||||
if (g_strcmp0(key, "Flags") == 0) {
|
||||
if (g_str_has_prefix(value, "~")) {
|
||||
FwupdDeviceFlags flag = fwupd_device_flag_from_string(value + 1);
|
||||
if (flag == FWUPD_DEVICE_FLAG_NOTIFIED) {
|
||||
fu_device_remove_flag(device, flag);
|
||||
} else {
|
||||
g_set_error(error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_NOT_SUPPORTED,
|
||||
"flag %s cannot be unset from client",
|
||||
key);
|
||||
return FALSE;
|
||||
}
|
||||
} else {
|
||||
FwupdDeviceFlags flag = fwupd_device_flag_from_string(value);
|
||||
if (flag == FWUPD_DEVICE_FLAG_REPORTED ||
|
||||
flag == FWUPD_DEVICE_FLAG_NOTIFIED) {
|
||||
fu_device_add_flag(device, flag);
|
||||
} else {
|
||||
g_set_error(error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_NOT_SUPPORTED,
|
||||
"flag %s cannot be set from client",
|
||||
key);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
return fu_history_modify_device(self->history, device, error);
|
||||
}
|
||||
|
||||
/* others invalid */
|
||||
if (g_strcmp0(key, "Flags") == 0)
|
||||
return fu_engine_modify_device_flags(self, device_id, value, error);
|
||||
g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "key %s not supported", key);
|
||||
return FALSE;
|
||||
}
|
||||
@ -3032,6 +3112,9 @@ fu_engine_install_release(FuEngine *self,
|
||||
fu_engine_wait_for_acquiesce(self, fu_device_get_acquiesce_delay(device_orig));
|
||||
}
|
||||
|
||||
/* allow capturing setup again */
|
||||
fu_engine_set_install_phase(self, FU_ENGINE_INSTALL_PHASE_SETUP);
|
||||
|
||||
/* make the UI update */
|
||||
fu_engine_emit_changed(self);
|
||||
|
||||
@ -3055,13 +3138,220 @@ fu_engine_get_plugins(FuEngine *self)
|
||||
return fu_plugin_list_get_all(self->plugin_list);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_engine_emulation_load_json(FuEngine *self, const gchar *json, GError **error)
|
||||
{
|
||||
JsonNode *root;
|
||||
g_autoptr(JsonParser) parser = json_parser_new();
|
||||
|
||||
/* parse */
|
||||
if (!json_parser_load_from_data(parser, json, -1, error))
|
||||
return FALSE;
|
||||
root = json_parser_get_root(parser);
|
||||
|
||||
/* load into all backends */
|
||||
for (guint i = 0; i < self->backends->len; i++) {
|
||||
FuBackend *backend = g_ptr_array_index(self->backends, i);
|
||||
if (!fu_backend_load(backend,
|
||||
json_node_get_object(root),
|
||||
FU_USB_DEVICE_EMULATION_TAG,
|
||||
FU_BACKEND_LOAD_FLAG_NONE,
|
||||
error))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* success */
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_engine_emulation_load_phase(FuEngine *self, FuEngineInstallPhase phase, GError **error)
|
||||
{
|
||||
const gchar *json = g_hash_table_lookup(self->emulation_phases, GINT_TO_POINTER(phase));
|
||||
if (json == NULL)
|
||||
return TRUE;
|
||||
if (g_getenv("FWUPD_BACKEND_VERBOSE") != NULL)
|
||||
g_debug("loading phase %s: %s", fu_engine_install_phase_to_string(phase), json);
|
||||
return fu_engine_emulation_load_json(self, json, error);
|
||||
}
|
||||
|
||||
gboolean
|
||||
fu_engine_emulation_load(FuEngine *self, GBytes *data, GError **error)
|
||||
{
|
||||
gboolean got_json = FALSE;
|
||||
g_autoptr(FuArchive) archive = NULL;
|
||||
|
||||
g_return_val_if_fail(FU_IS_ENGINE(self), FALSE);
|
||||
g_return_val_if_fail(data != NULL, FALSE);
|
||||
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
|
||||
|
||||
/* not supported */
|
||||
if (!fu_config_get_allow_emulation(self->config)) {
|
||||
g_set_error_literal(error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_NOT_SUPPORTED,
|
||||
"emulation is not allowed from config");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* unload any existing devices */
|
||||
if (!fu_engine_emulation_load_json(self, "{\"UsbDevices\":[]}", error))
|
||||
return FALSE;
|
||||
|
||||
/* load archive */
|
||||
archive = fu_archive_new(data, FU_ARCHIVE_FLAG_NONE, error);
|
||||
if (archive == NULL)
|
||||
return FALSE;
|
||||
|
||||
/* load JSON files from archive */
|
||||
g_hash_table_remove_all(self->emulation_phases);
|
||||
for (guint phase = FU_ENGINE_INSTALL_PHASE_SETUP; phase < FU_ENGINE_INSTALL_PHASE_LAST;
|
||||
phase++) {
|
||||
g_autofree gchar *fn =
|
||||
g_strdup_printf("%s.json", fu_engine_install_phase_to_string(phase));
|
||||
g_autofree gchar *json_safe = NULL;
|
||||
GBytes *blob = fu_archive_lookup_by_fn(archive, fn, NULL);
|
||||
|
||||
/* not found */
|
||||
if (blob == NULL)
|
||||
continue;
|
||||
json_safe = g_strndup(g_bytes_get_data(blob, NULL), g_bytes_get_size(blob));
|
||||
got_json = TRUE;
|
||||
g_debug("got emulation for phase %s", fu_engine_install_phase_to_string(phase));
|
||||
if (phase == FU_ENGINE_INSTALL_PHASE_SETUP) {
|
||||
if (!fu_engine_emulation_load_json(self, json_safe, error))
|
||||
return FALSE;
|
||||
} else {
|
||||
g_hash_table_insert(self->emulation_phases,
|
||||
GINT_TO_POINTER(phase),
|
||||
g_steal_pointer(&json_safe));
|
||||
}
|
||||
}
|
||||
if (!got_json) {
|
||||
g_set_error_literal(error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_NOT_SUPPORTED,
|
||||
"no emulation data found in archive");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* success */
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
GBytes *
|
||||
fu_engine_emulation_save(FuEngine *self, GError **error)
|
||||
{
|
||||
gboolean got_json = FALSE;
|
||||
g_autoptr(GBytes) bytes = NULL;
|
||||
g_autoptr(FuArchive) archive = fu_archive_new(NULL, FU_ARCHIVE_FLAG_NONE, NULL);
|
||||
|
||||
g_return_val_if_fail(FU_IS_ENGINE(self), NULL);
|
||||
g_return_val_if_fail(error == NULL || *error == NULL, NULL);
|
||||
|
||||
/* not supported */
|
||||
if (!fu_config_get_allow_emulation(self->config)) {
|
||||
g_set_error_literal(error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_NOT_SUPPORTED,
|
||||
"emulation is not allowed from config");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* sanity check */
|
||||
for (guint phase = FU_ENGINE_INSTALL_PHASE_SETUP; phase < FU_ENGINE_INSTALL_PHASE_LAST;
|
||||
phase++) {
|
||||
const gchar *json =
|
||||
g_hash_table_lookup(self->emulation_phases, GINT_TO_POINTER(phase));
|
||||
g_autofree gchar *fn =
|
||||
g_strdup_printf("%s.json", fu_engine_install_phase_to_string(phase));
|
||||
g_autoptr(GBytes) blob = NULL;
|
||||
|
||||
/* nothing set */
|
||||
if (json == NULL)
|
||||
continue;
|
||||
got_json = TRUE;
|
||||
blob = g_bytes_new_static(json, strlen(json));
|
||||
fu_archive_add_entry(archive, fn, blob);
|
||||
}
|
||||
if (!got_json) {
|
||||
g_set_error_literal(error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_NOT_SUPPORTED,
|
||||
"no emulation data, perhaps no devices have been added?");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* write */
|
||||
bytes =
|
||||
fu_archive_write(archive, FU_ARCHIVE_FORMAT_ZIP, FU_ARCHIVE_COMPRESSION_GZIP, error);
|
||||
if (bytes == NULL)
|
||||
return NULL;
|
||||
|
||||
/* success */
|
||||
g_hash_table_remove_all(self->emulation_phases);
|
||||
return g_steal_pointer(&bytes);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_engine_backends_save_phase(FuEngine *self, GError **error)
|
||||
{
|
||||
const gchar *data_old;
|
||||
g_autofree gchar *data_new = NULL;
|
||||
g_autoptr(JsonBuilder) json_builder = json_builder_new();
|
||||
g_autoptr(JsonGenerator) json_generator = NULL;
|
||||
g_autoptr(JsonNode) json_root = NULL;
|
||||
|
||||
/* all devices in all backends */
|
||||
for (guint i = 0; i < self->backends->len; i++) {
|
||||
FuBackend *backend = g_ptr_array_index(self->backends, i);
|
||||
if (!fu_backend_save(backend,
|
||||
json_builder,
|
||||
FU_USB_DEVICE_EMULATION_TAG,
|
||||
FU_BACKEND_SAVE_FLAG_NONE,
|
||||
error))
|
||||
return FALSE;
|
||||
}
|
||||
json_root = json_builder_get_root(json_builder);
|
||||
json_generator = json_generator_new();
|
||||
json_generator_set_pretty(json_generator, TRUE);
|
||||
json_generator_set_root(json_generator, json_root);
|
||||
|
||||
data_old =
|
||||
g_hash_table_lookup(self->emulation_phases, GINT_TO_POINTER(self->install_phase));
|
||||
data_new = json_generator_to_data(json_generator, NULL);
|
||||
if (g_strcmp0(data_new, "") == 0) {
|
||||
g_debug("no data for phase %s",
|
||||
fu_engine_install_phase_to_string(self->install_phase));
|
||||
return TRUE;
|
||||
}
|
||||
if (g_strcmp0(data_old, data_new) == 0) {
|
||||
g_debug("JSON unchanged for phase %s",
|
||||
fu_engine_install_phase_to_string(self->install_phase));
|
||||
return TRUE;
|
||||
}
|
||||
if (g_getenv("FWUPD_BACKEND_VERBOSE") != NULL) {
|
||||
g_autofree gchar *data_new_safe = data_new_safe = g_strndup(data_new, 8000);
|
||||
g_debug("JSON %s for phase %s: %s...",
|
||||
data_old == NULL ? "added" : "changed",
|
||||
fu_engine_install_phase_to_string(self->install_phase),
|
||||
data_new_safe);
|
||||
}
|
||||
g_hash_table_insert(self->emulation_phases,
|
||||
GINT_TO_POINTER(self->install_phase),
|
||||
g_steal_pointer(&data_new));
|
||||
|
||||
/* success */
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* fu_engine_get_device:
|
||||
* @self: a #FuEngine
|
||||
* @device_id: a device ID
|
||||
* @error: (nullable): optional return location for an error
|
||||
*
|
||||
* Gets a specific device.
|
||||
* Gets a specific device, optionally loading an emulated phase.
|
||||
*
|
||||
* Returns: (transfer full): a device, or %NULL if not found
|
||||
**/
|
||||
@ -3070,6 +3360,17 @@ fu_engine_get_device(FuEngine *self, const gchar *device_id, GError **error)
|
||||
{
|
||||
g_autoptr(FuDevice) device = NULL;
|
||||
|
||||
/* we are emulating a device */
|
||||
if (self->install_phase != FU_ENGINE_INSTALL_PHASE_SETUP) {
|
||||
g_autoptr(FuDevice) device_old = NULL;
|
||||
device_old = fu_device_list_get_by_id(self->device_list, device_id, NULL);
|
||||
if (device_old != NULL &&
|
||||
fu_device_has_flag(device_old, FWUPD_DEVICE_FLAG_EMULATED)) {
|
||||
if (!fu_engine_emulation_load_phase(self, self->install_phase, error))
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* wait for any device to disconnect and reconnect */
|
||||
if (!fu_device_list_wait_for_replug(self->device_list, error)) {
|
||||
g_prefix_error(error, "failed to wait for detach replug: ");
|
||||
@ -3214,6 +3515,12 @@ fu_engine_prepare(FuEngine *self,
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* save to emulated phase */
|
||||
if (fu_context_has_flag(self->ctx, FU_CONTEXT_FLAG_SAVE_EVENTS) &&
|
||||
!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED)) {
|
||||
if (!fu_engine_backends_save_phase(self, error))
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@ -3245,6 +3552,12 @@ fu_engine_cleanup(FuEngine *self,
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* save to emulated phase */
|
||||
if (fu_context_has_flag(self->ctx, FU_CONTEXT_FLAG_SAVE_EVENTS) &&
|
||||
!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED)) {
|
||||
if (!fu_engine_backends_save_phase(self, error))
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@ -3301,6 +3614,13 @@ fu_engine_detach(FuEngine *self,
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* save to emulated phase */
|
||||
if (fu_context_has_flag(self->ctx, FU_CONTEXT_FLAG_SAVE_EVENTS) &&
|
||||
!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED)) {
|
||||
if (!fu_engine_backends_save_phase(self, error))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* success */
|
||||
return TRUE;
|
||||
}
|
||||
@ -3333,6 +3653,14 @@ fu_engine_attach(FuEngine *self, const gchar *device_id, FuProgress *progress, G
|
||||
|
||||
if (!fu_plugin_runner_attach(plugin, device, progress, error))
|
||||
return FALSE;
|
||||
|
||||
/* save to emulated phase */
|
||||
if (fu_context_has_flag(self->ctx, FU_CONTEXT_FLAG_SAVE_EVENTS) &&
|
||||
!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED)) {
|
||||
if (!fu_engine_backends_save_phase(self, error))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@ -3412,6 +3740,15 @@ fu_engine_reload(FuEngine *self, const gchar *device_id, GError **error)
|
||||
g_prefix_error(error, "failed to reload device: ");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* save to emulated phase */
|
||||
if (fu_context_has_flag(self->ctx, FU_CONTEXT_FLAG_SAVE_EVENTS) &&
|
||||
!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED)) {
|
||||
if (!fu_engine_backends_save_phase(self, error))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* success */
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@ -3457,11 +3794,13 @@ fu_engine_write_firmware(FuEngine *self,
|
||||
g_autoptr(GError) error_cleanup = NULL;
|
||||
|
||||
/* attack back into runtime then cleanup */
|
||||
fu_engine_set_install_phase(self, FU_ENGINE_INSTALL_PHASE_ATTACH);
|
||||
fu_progress_reset(progress);
|
||||
if (!fu_plugin_runner_attach(plugin, device, progress, &error_attach)) {
|
||||
g_warning("failed to attach device after failed update: %s",
|
||||
error_attach->message);
|
||||
}
|
||||
fu_engine_set_install_phase(self, FU_ENGINE_INSTALL_PHASE_CLEANUP);
|
||||
fu_progress_reset(progress);
|
||||
if (!fu_engine_cleanup(self, device_id, progress, flags, &error_cleanup)) {
|
||||
g_warning("failed to update-cleanup after failed update: %s",
|
||||
@ -3498,6 +3837,15 @@ fu_engine_write_firmware(FuEngine *self,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* save to emulated phase */
|
||||
if (fu_context_has_flag(self->ctx, FU_CONTEXT_FLAG_SAVE_EVENTS) &&
|
||||
!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED)) {
|
||||
if (!fu_engine_backends_save_phase(self, error))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* success */
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@ -3659,7 +4007,6 @@ fu_engine_install_blob(FuEngine *self,
|
||||
fu_progress_step_done(progress_local);
|
||||
|
||||
/* the device and plugin both may have changed */
|
||||
fu_engine_set_install_phase(self, FU_ENGINE_INSTALL_PHASE_UNKNOWN);
|
||||
device_tmp = fu_engine_get_device(self, device_id, error);
|
||||
if (device_tmp == NULL) {
|
||||
g_prefix_error(error, "failed to get device after install blob: ");
|
||||
@ -6353,6 +6700,25 @@ fu_engine_device_inherit_history(FuEngine *self, FuDevice *device)
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
fu_engine_ensure_device_emulation_tag(FuEngine *self, FuDevice *device)
|
||||
{
|
||||
/* already done */
|
||||
if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATION_TAG))
|
||||
return;
|
||||
|
||||
/* we matched this physical ID */
|
||||
if (fu_device_get_backend_id(device) == NULL)
|
||||
return;
|
||||
if (!g_hash_table_contains(self->emulation_backend_ids, fu_device_get_backend_id(device)))
|
||||
return;
|
||||
|
||||
/* success */
|
||||
g_debug("adding emulation-tag to %s", fu_device_get_backend_id(device));
|
||||
fu_device_add_flag(device, FWUPD_DEVICE_FLAG_EMULATION_TAG);
|
||||
fu_engine_check_context_flag_save_events(self);
|
||||
}
|
||||
|
||||
void
|
||||
fu_engine_add_device(FuEngine *self, FuDevice *device)
|
||||
{
|
||||
@ -6432,6 +6798,9 @@ fu_engine_add_device(FuEngine *self, FuDevice *device)
|
||||
}
|
||||
}
|
||||
|
||||
/* check if the device needs emulation-tag */
|
||||
fu_engine_ensure_device_emulation_tag(self, device);
|
||||
|
||||
/* set or clear the SUPPORTED flag */
|
||||
fu_engine_ensure_device_supported(self, device);
|
||||
|
||||
@ -6482,6 +6851,16 @@ fu_engine_add_device(FuEngine *self, FuDevice *device)
|
||||
if (component != NULL)
|
||||
fu_engine_md_refresh_device_from_component(self, device, component);
|
||||
|
||||
/* save to emulated phase, but avoid overwriting reload */
|
||||
if (fu_context_has_flag(self->ctx, FU_CONTEXT_FLAG_SAVE_EVENTS) &&
|
||||
self->install_phase == FU_ENGINE_INSTALL_PHASE_SETUP &&
|
||||
fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATION_TAG) &&
|
||||
!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED)) {
|
||||
g_autoptr(GError) error_local = NULL;
|
||||
if (!fu_engine_backends_save_phase(self, &error_local))
|
||||
g_warning("failed to save phase: %s", error_local->message);
|
||||
}
|
||||
|
||||
fu_engine_emit_changed(self);
|
||||
}
|
||||
|
||||
@ -7438,6 +7817,9 @@ fu_engine_backend_device_added(FuEngine *self, FuDevice *device, FuProgress *pro
|
||||
}
|
||||
fu_progress_step_done(progress);
|
||||
|
||||
/* check if the device needs emulation-tag */
|
||||
fu_engine_ensure_device_emulation_tag(self, device);
|
||||
|
||||
/* super useful for plugin development */
|
||||
if (g_getenv("FWUPD_PROBE_VERBOSE") != NULL) {
|
||||
g_autofree gchar *str = fu_device_to_string(FU_DEVICE(device));
|
||||
@ -8438,22 +8820,6 @@ fu_engine_idle_status_notify_cb(FuIdle *idle, GParamSpec *pspec, FuEngine *self)
|
||||
fu_engine_set_status(self, status);
|
||||
}
|
||||
|
||||
gboolean
|
||||
fu_engine_backends_save(FuEngine *self, JsonBuilder *json_builder, GError **error)
|
||||
{
|
||||
json_builder_begin_object(json_builder);
|
||||
json_builder_set_member_name(json_builder, "Backends");
|
||||
json_builder_begin_array(json_builder);
|
||||
for (guint i = 0; i < self->backends->len; i++) {
|
||||
FuBackend *backend = g_ptr_array_index(self->backends, i);
|
||||
if (!fu_backend_save(backend, json_builder, NULL, FU_BACKEND_SAVE_FLAG_NONE, error))
|
||||
return FALSE;
|
||||
}
|
||||
json_builder_end_array(json_builder);
|
||||
json_builder_end_object(json_builder);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
fu_engine_init(FuEngine *self)
|
||||
{
|
||||
@ -8479,6 +8845,8 @@ fu_engine_init(FuEngine *self)
|
||||
self->runtime_versions = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
|
||||
self->compile_versions = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
|
||||
self->acquiesce_loop = g_main_loop_new(NULL, FALSE);
|
||||
self->emulation_phases = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_free);
|
||||
self->emulation_backend_ids = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
|
||||
|
||||
fu_context_set_runtime_versions(self->ctx, self->runtime_versions);
|
||||
fu_context_set_compile_versions(self->ctx, self->compile_versions);
|
||||
@ -8639,6 +9007,8 @@ fu_engine_finalize(GObject *obj)
|
||||
g_ptr_array_unref(self->local_monitors);
|
||||
g_hash_table_unref(self->runtime_versions);
|
||||
g_hash_table_unref(self->compile_versions);
|
||||
g_hash_table_unref(self->emulation_phases);
|
||||
g_hash_table_unref(self->emulation_backend_ids);
|
||||
g_object_unref(self->plugin_list);
|
||||
|
||||
G_OBJECT_CLASS(fu_engine_parent_class)->finalize(obj);
|
||||
|
@ -250,4 +250,6 @@ fu_engine_modify_bios_settings(FuEngine *self,
|
||||
gboolean force_ro,
|
||||
GError **error);
|
||||
gboolean
|
||||
fu_engine_backends_save(FuEngine *self, JsonBuilder *json_builder, GError **error);
|
||||
fu_engine_emulation_load(FuEngine *self, GBytes *data, GError **error);
|
||||
GBytes *
|
||||
fu_engine_emulation_save(FuEngine *self, GError **error);
|
||||
|
@ -3243,7 +3243,7 @@ fu_backend_usb_func(gconstpointer user_data)
|
||||
g_autoptr(GPtrArray) devices = NULL;
|
||||
g_autoptr(GPtrArray) possible_plugins = NULL;
|
||||
|
||||
#if !G_USB_CHECK_VERSION(0, 4, 4)
|
||||
#if !G_USB_CHECK_VERSION(0, 4, 5)
|
||||
g_test_skip("GUsb version too old");
|
||||
return;
|
||||
#endif
|
||||
@ -3332,7 +3332,7 @@ fu_backend_usb_invalid_func(gconstpointer user_data)
|
||||
g_autoptr(GPtrArray) devices = NULL;
|
||||
g_autoptr(JsonParser) parser = json_parser_new();
|
||||
|
||||
#if !G_USB_CHECK_VERSION(0, 4, 2)
|
||||
#if !G_USB_CHECK_VERSION(0, 4, 5)
|
||||
g_test_skip("GUsb version too old");
|
||||
return;
|
||||
#endif
|
||||
|
@ -3441,34 +3441,6 @@ fu_util_setup_interactive(FuUtilPrivate *priv, GError **error)
|
||||
return fu_util_setup_interactive_console(error);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_util_backends_save(FuUtilPrivate *priv, const gchar *fn, GError **error)
|
||||
{
|
||||
g_autofree gchar *data = NULL;
|
||||
g_autoptr(JsonBuilder) json_builder = json_builder_new();
|
||||
g_autoptr(JsonGenerator) json_generator = NULL;
|
||||
g_autoptr(JsonNode) json_root = NULL;
|
||||
|
||||
/* export as a string */
|
||||
if (!fu_engine_backends_save(priv->engine, json_builder, error))
|
||||
return FALSE;
|
||||
json_root = json_builder_get_root(json_builder);
|
||||
json_generator = json_generator_new();
|
||||
json_generator_set_pretty(json_generator, TRUE);
|
||||
json_generator_set_root(json_generator, json_root);
|
||||
data = json_generator_to_data(json_generator, NULL);
|
||||
if (data == NULL) {
|
||||
g_set_error_literal(error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_INVALID_DATA,
|
||||
"Failed to convert to JSON string");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* save to file */
|
||||
return g_file_set_contents(fn, data, -1, error);
|
||||
}
|
||||
|
||||
static void
|
||||
fu_util_print_error(FuUtilPrivate *priv, const GError *error)
|
||||
{
|
||||
@ -3498,7 +3470,6 @@ main(int argc, char *argv[])
|
||||
g_autoptr(GPtrArray) cmd_array = fu_util_cmd_array_new();
|
||||
g_autofree gchar *cmd_descriptions = NULL;
|
||||
g_autofree gchar *filter = NULL;
|
||||
g_autofree gchar *save_backends_fn = NULL;
|
||||
const GOptionEntry options[] = {
|
||||
{"version",
|
||||
'\0',
|
||||
@ -3653,15 +3624,6 @@ main(int argc, char *argv[])
|
||||
N_("Filter with a set of device flags using a ~ prefix to "
|
||||
"exclude, e.g. 'internal,~needs-reboot'"),
|
||||
NULL},
|
||||
{"save-backends",
|
||||
'\0',
|
||||
0,
|
||||
G_OPTION_ARG_STRING,
|
||||
&save_backends_fn,
|
||||
/* TRANSLATORS: command line option */
|
||||
N_("Specify a filename to use to save backend events"),
|
||||
/* TRANSLATORS: filename argument with path */
|
||||
N_("FILENAME")},
|
||||
{"json",
|
||||
'\0',
|
||||
0,
|
||||
@ -4103,10 +4065,6 @@ main(int argc, char *argv[])
|
||||
|
||||
/* load engine */
|
||||
priv->engine = fu_engine_new();
|
||||
if (save_backends_fn != NULL) {
|
||||
fu_context_add_flag(fu_engine_get_context(priv->engine),
|
||||
FU_CONTEXT_FLAG_SAVE_EVENTS);
|
||||
}
|
||||
g_signal_connect(FU_ENGINE(priv->engine),
|
||||
"device-request",
|
||||
G_CALLBACK(fu_util_update_device_request_cb),
|
||||
@ -4165,12 +4123,6 @@ main(int argc, char *argv[])
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
/* dump devices */
|
||||
if (save_backends_fn != NULL && !fu_util_backends_save(priv, save_backends_fn, &error)) {
|
||||
g_printerr("%s\n", error->message);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
/* a good place to do the traceback */
|
||||
if (fu_progress_get_profile(priv->progress)) {
|
||||
g_autofree gchar *str = fu_progress_traceback(priv->progress);
|
||||
|
@ -50,52 +50,18 @@ fu_usb_backend_device_notify_flags_cb(FuDevice *device, GParamSpec *pspec, FuBac
|
||||
static void
|
||||
fu_usb_backend_device_added_cb(GUsbContext *ctx, GUsbDevice *usb_device, FuBackend *backend)
|
||||
{
|
||||
FuDevice *device_tmp;
|
||||
g_autoptr(FuUsbDevice) device = NULL;
|
||||
|
||||
/* is emulated? */
|
||||
device_tmp = fu_backend_lookup_by_id(backend, g_usb_device_get_platform_id(usb_device));
|
||||
if (device_tmp != NULL && fu_device_has_flag(device_tmp, FWUPD_DEVICE_FLAG_EMULATED)) {
|
||||
GUsbDevice *usb_device_tmp = fu_usb_device_get_dev(FU_USB_DEVICE(device_tmp));
|
||||
#if G_USB_CHECK_VERSION(0, 4, 5)
|
||||
if (g_date_time_equal(g_usb_device_get_created(usb_device),
|
||||
g_usb_device_get_created(usb_device_tmp))) {
|
||||
#else
|
||||
if (g_usb_device_get_vid(usb_device) == g_usb_device_get_vid(usb_device_tmp) &&
|
||||
g_usb_device_get_pid(usb_device) == g_usb_device_get_pid(usb_device_tmp)) {
|
||||
#endif
|
||||
g_debug("replacing GUsbDevice of emulated device %s",
|
||||
fu_usb_device_get_platform_id(FU_USB_DEVICE(device_tmp)));
|
||||
fu_usb_device_set_dev(FU_USB_DEVICE(device_tmp), usb_device);
|
||||
fu_backend_device_changed(backend, device_tmp);
|
||||
return;
|
||||
}
|
||||
g_debug("delayed removal as emulated device changed");
|
||||
fu_backend_device_removed(backend, device_tmp);
|
||||
}
|
||||
|
||||
/* success */
|
||||
device = fu_usb_device_new(fu_backend_get_context(backend), usb_device);
|
||||
g_autoptr(FuUsbDevice) device =
|
||||
fu_usb_device_new(fu_backend_get_context(backend), usb_device);
|
||||
fu_backend_device_added(backend, FU_DEVICE(device));
|
||||
}
|
||||
|
||||
static void
|
||||
fu_usb_backend_device_removed_cb(GUsbContext *ctx, GUsbDevice *usb_device, FuBackend *backend)
|
||||
{
|
||||
FuUsbBackend *self = FU_USB_BACKEND(backend);
|
||||
FuDevice *device_tmp;
|
||||
|
||||
/* find the device we enumerated */
|
||||
device_tmp =
|
||||
fu_backend_lookup_by_id(FU_BACKEND(self), g_usb_device_get_platform_id(usb_device));
|
||||
if (device_tmp != NULL) {
|
||||
if (fu_device_has_flag(device_tmp, FWUPD_DEVICE_FLAG_EMULATED)) {
|
||||
g_debug("ignoring removal of emulated device %s",
|
||||
fu_device_get_id(device_tmp));
|
||||
return;
|
||||
}
|
||||
fu_backend_device_removed(backend, device_tmp);
|
||||
}
|
||||
FuDevice *device =
|
||||
fu_backend_lookup_by_id(backend, g_usb_device_get_platform_id(usb_device));
|
||||
if (device != NULL)
|
||||
fu_backend_device_removed(backend, device);
|
||||
}
|
||||
|
||||
static void
|
||||
@ -107,13 +73,11 @@ fu_usb_backend_context_finalized_cb(gpointer data, GObject *where_the_object_was
|
||||
static void
|
||||
fu_usb_backend_context_flags_check(FuUsbBackend *self)
|
||||
{
|
||||
#if G_USB_CHECK_VERSION(0, 4, 1)
|
||||
#if G_USB_CHECK_VERSION(0, 4, 5)
|
||||
FuContext *ctx = fu_backend_get_context(FU_BACKEND(self));
|
||||
GUsbContextFlags usb_flags = G_USB_CONTEXT_FLAGS_NONE;
|
||||
#if G_USB_CHECK_VERSION(0, 4, 4)
|
||||
if (g_getenv("FWUPD_BACKEND_VERBOSE") != NULL)
|
||||
usb_flags |= G_USB_CONTEXT_FLAGS_DEBUG;
|
||||
#endif
|
||||
if (fu_context_has_flag(ctx, FU_CONTEXT_FLAG_SAVE_EVENTS)) {
|
||||
g_debug("saving FuUsbBackend events");
|
||||
usb_flags |= G_USB_CONTEXT_FLAGS_SAVE_EVENTS;
|
||||
@ -213,7 +177,7 @@ fu_usb_backend_load(FuBackend *backend,
|
||||
FuBackendLoadFlags flags,
|
||||
GError **error)
|
||||
{
|
||||
#if G_USB_CHECK_VERSION(0, 4, 1)
|
||||
#if G_USB_CHECK_VERSION(0, 4, 5)
|
||||
FuUsbBackend *self = FU_USB_BACKEND(backend);
|
||||
return g_usb_context_load_with_tag(self->usb_ctx, json_object, tag, error);
|
||||
#else
|
||||
@ -232,9 +196,30 @@ fu_usb_backend_save(FuBackend *backend,
|
||||
FuBackendSaveFlags flags,
|
||||
GError **error)
|
||||
{
|
||||
#if G_USB_CHECK_VERSION(0, 4, 1)
|
||||
#if G_USB_CHECK_VERSION(0, 4, 5)
|
||||
FuUsbBackend *self = FU_USB_BACKEND(backend);
|
||||
return g_usb_context_save_with_tag(self->usb_ctx, json_builder, tag, error);
|
||||
guint usb_events_cnt = 0;
|
||||
g_autoptr(GPtrArray) devices = g_usb_context_get_devices(self->usb_ctx);
|
||||
|
||||
for (guint i = 0; i < devices->len; i++) {
|
||||
GUsbDevice *usb_device = g_ptr_array_index(devices, i);
|
||||
g_autoptr(GPtrArray) usb_events = g_usb_device_get_events(usb_device);
|
||||
if (usb_events->len > 0 || g_usb_device_has_tag(usb_device, tag)) {
|
||||
g_debug("%u USB events to save for %s",
|
||||
usb_events->len,
|
||||
g_usb_device_get_platform_id(usb_device));
|
||||
}
|
||||
usb_events_cnt += usb_events->len;
|
||||
}
|
||||
if (usb_events_cnt == 0)
|
||||
return TRUE;
|
||||
if (!g_usb_context_save_with_tag(self->usb_ctx, json_builder, tag, error))
|
||||
return FALSE;
|
||||
for (guint i = 0; i < devices->len; i++) {
|
||||
GUsbDevice *usb_device = g_ptr_array_index(devices, i);
|
||||
g_usb_device_clear_events(usb_device);
|
||||
}
|
||||
return TRUE;
|
||||
#else
|
||||
g_set_error_literal(error,
|
||||
G_IO_ERROR,
|
||||
|
@ -1316,6 +1316,10 @@ fu_util_device_flag_to_string(guint64 device_flag)
|
||||
/* TRANSLATORS: this device is not actually real */
|
||||
return _("Emulated");
|
||||
}
|
||||
if (device_flag == FWUPD_DEVICE_FLAG_EMULATION_TAG) {
|
||||
/* TRANSLATORS: we're saving all USB events for emulation */
|
||||
return _("Tagged for emulation");
|
||||
}
|
||||
if (device_flag == FWUPD_DEVICE_FLAG_SKIPS_RESTART) {
|
||||
/* skip */
|
||||
return NULL;
|
||||
|
186
src/fu-util.c
186
src/fu-util.c
@ -689,6 +689,7 @@ typedef struct {
|
||||
guint nr_missing;
|
||||
JsonBuilder *builder;
|
||||
const gchar *name;
|
||||
gboolean use_emulation;
|
||||
} FuUtilDeviceTestHelper;
|
||||
|
||||
static gboolean
|
||||
@ -814,12 +815,45 @@ fu_util_device_test_step(FuUtilPrivate *priv,
|
||||
{
|
||||
JsonArray *json_array;
|
||||
const gchar *url;
|
||||
const gchar *emulation_url = NULL;
|
||||
const gchar *baseuri = g_getenv("FWUPD_DEVICE_TESTS_BASE_URI");
|
||||
g_autofree gchar *filename = NULL;
|
||||
g_autofree gchar *url_safe = NULL;
|
||||
g_autoptr(GBytes) fw = NULL;
|
||||
g_autoptr(GError) error_local = NULL;
|
||||
|
||||
/* send this data to the daemon */
|
||||
if (helper->use_emulation) {
|
||||
g_autofree gchar *emulation_filename = NULL;
|
||||
g_autofree gchar *emulation_safe = NULL;
|
||||
g_autoptr(GBytes) emulation_data = NULL;
|
||||
|
||||
/* just ignore anything without emulation data */
|
||||
if (!json_object_has_member(json_obj, "emulation-url"))
|
||||
return TRUE;
|
||||
|
||||
emulation_url = json_object_get_string_member(json_obj, "emulation-url");
|
||||
if (baseuri != NULL) {
|
||||
g_autofree gchar *basename = g_path_get_basename(emulation_url);
|
||||
emulation_safe = g_build_filename(baseuri, basename, NULL);
|
||||
} else {
|
||||
emulation_safe = g_strdup(emulation_url);
|
||||
}
|
||||
emulation_filename = fu_util_download_if_required(priv, emulation_safe, error);
|
||||
if (emulation_filename == NULL) {
|
||||
g_prefix_error(error, "failed to download %s: ", emulation_safe);
|
||||
return FALSE;
|
||||
}
|
||||
emulation_data = fu_bytes_get_contents(emulation_filename, error);
|
||||
if (emulation_data == NULL)
|
||||
return FALSE;
|
||||
if (!fwupd_client_emulation_load(priv->client,
|
||||
emulation_data,
|
||||
priv->cancellable,
|
||||
error))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* download file if required */
|
||||
if (!json_object_has_member(json_obj, "url")) {
|
||||
g_set_error_literal(error,
|
||||
@ -1028,13 +1062,13 @@ fu_util_quit(FuUtilPrivate *priv, gchar **values, GError **error)
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_util_device_test(FuUtilPrivate *priv, gchar **values, GError **error)
|
||||
fu_util_device_test_full(FuUtilPrivate *priv,
|
||||
gchar **values,
|
||||
FuUtilDeviceTestHelper *helper,
|
||||
GError **error)
|
||||
{
|
||||
g_autoptr(JsonBuilder) builder = json_builder_new();
|
||||
FuUtilDeviceTestHelper helper = {.nr_failed = 0,
|
||||
.nr_success = 0,
|
||||
.builder = builder,
|
||||
.name = "Unknown"};
|
||||
helper->builder = builder;
|
||||
|
||||
/* required for interactive devices */
|
||||
priv->current_operation = FU_UTIL_OPERATION_UPDATE;
|
||||
@ -1056,7 +1090,7 @@ fu_util_device_test(FuUtilPrivate *priv, gchar **values, GError **error)
|
||||
json_builder_begin_array(builder);
|
||||
for (guint i = 0; values[i] != NULL; i++) {
|
||||
json_builder_begin_object(builder);
|
||||
if (!fu_util_device_test_filename(priv, &helper, values[i], error))
|
||||
if (!fu_util_device_test_filename(priv, helper, values[i], error))
|
||||
return FALSE;
|
||||
json_builder_end_object(builder);
|
||||
}
|
||||
@ -1070,23 +1104,23 @@ fu_util_device_test(FuUtilPrivate *priv, gchar **values, GError **error)
|
||||
}
|
||||
|
||||
/* we need all to pass for a zero return code */
|
||||
if (helper.nr_failed > 0) {
|
||||
if (helper->nr_failed > 0) {
|
||||
g_set_error_literal(error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_NOT_SUPPORTED,
|
||||
"Some of the tests failed");
|
||||
return FALSE;
|
||||
}
|
||||
if (helper.nr_missing > 0) {
|
||||
if (helper->nr_missing > 0) {
|
||||
g_set_error(error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_NOT_SUPPORTED,
|
||||
"%u devices required for %u tests were not found",
|
||||
helper.nr_missing,
|
||||
helper->nr_missing,
|
||||
g_strv_length(values));
|
||||
return FALSE;
|
||||
}
|
||||
if (helper.nr_success == 0) {
|
||||
if (helper->nr_success == 0) {
|
||||
g_set_error_literal(error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_NOT_SUPPORTED,
|
||||
@ -1102,6 +1136,20 @@ fu_util_device_test(FuUtilPrivate *priv, gchar **values, GError **error)
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_util_device_emulate(FuUtilPrivate *priv, gchar **values, GError **error)
|
||||
{
|
||||
FuUtilDeviceTestHelper helper = {.use_emulation = TRUE};
|
||||
return fu_util_device_test_full(priv, values, &helper, error);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_util_device_test(FuUtilPrivate *priv, gchar **values, GError **error)
|
||||
{
|
||||
FuUtilDeviceTestHelper helper = {.use_emulation = FALSE};
|
||||
return fu_util_device_test_full(priv, values, &helper, error);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_util_download(FuUtilPrivate *priv, gchar **values, GError **error)
|
||||
{
|
||||
@ -4025,6 +4073,89 @@ fu_util_get_bios_setting(FuUtilPrivate *priv, gchar **values, GError **error)
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_util_emulation_tag(FuUtilPrivate *priv, gchar **values, GError **error)
|
||||
{
|
||||
g_autoptr(FwupdDevice) dev = NULL;
|
||||
g_autofree gchar *cmd = g_strdup_printf("%s emulation-save", g_get_prgname());
|
||||
|
||||
/* set the flag */
|
||||
dev = fu_util_get_device_or_prompt(priv, values, error);
|
||||
if (dev == NULL)
|
||||
return FALSE;
|
||||
if (!fwupd_client_modify_device(priv->client,
|
||||
fwupd_device_get_id(dev),
|
||||
"Flags",
|
||||
"emulation-tag",
|
||||
priv->cancellable,
|
||||
error))
|
||||
return FALSE;
|
||||
|
||||
/* TRANSLATORS: these are instructions on how to generate the data, and the %1 is a command
|
||||
* like 'fwupdmgr emulation-save' */
|
||||
g_print(_("Now unplug and replug the device, install the firmware, and then run %s"), cmd);
|
||||
g_print("\n");
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_util_emulation_untag(FuUtilPrivate *priv, gchar **values, GError **error)
|
||||
{
|
||||
g_autoptr(FwupdDevice) dev = NULL;
|
||||
|
||||
/* set the flag */
|
||||
priv->filter_include |= FWUPD_DEVICE_FLAG_EMULATION_TAG;
|
||||
dev = fu_util_get_device_or_prompt(priv, values, error);
|
||||
if (dev == NULL)
|
||||
return FALSE;
|
||||
return fwupd_client_modify_device(priv->client,
|
||||
fwupd_device_get_id(dev),
|
||||
"Flags",
|
||||
"~emulation-tag",
|
||||
priv->cancellable,
|
||||
error);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_util_emulation_save(FuUtilPrivate *priv, gchar **values, GError **error)
|
||||
{
|
||||
g_autoptr(GBytes) data = NULL;
|
||||
|
||||
/* check args */
|
||||
if (g_strv_length(values) != 1) {
|
||||
g_set_error_literal(error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_INVALID_ARGS,
|
||||
"Invalid arguments, expected FILENAME");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* save */
|
||||
data = fwupd_client_emulation_save(priv->client, priv->cancellable, error);
|
||||
if (data == NULL)
|
||||
return FALSE;
|
||||
return fu_bytes_set_contents(values[0], data, error);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_util_emulation_load(FuUtilPrivate *priv, gchar **values, GError **error)
|
||||
{
|
||||
g_autoptr(GBytes) data = NULL;
|
||||
|
||||
/* check args */
|
||||
if (g_strv_length(values) != 1) {
|
||||
g_set_error_literal(error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_INVALID_ARGS,
|
||||
"Invalid arguments, expected FILENAME");
|
||||
return FALSE;
|
||||
}
|
||||
data = fu_bytes_get_contents(values[0], error);
|
||||
if (data == NULL)
|
||||
return FALSE;
|
||||
return fwupd_client_emulation_load(priv->client, data, priv->cancellable, error);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_util_version(FuUtilPrivate *priv, GError **error)
|
||||
{
|
||||
@ -4555,6 +4686,13 @@ main(int argc, char *argv[])
|
||||
/* TRANSLATORS: command description */
|
||||
_("Test a device using a JSON manifest"),
|
||||
fu_util_device_test);
|
||||
fu_util_cmd_array_add(cmd_array,
|
||||
"device-emulate",
|
||||
/* TRANSLATORS: command argument: uppercase, spaces->dashes */
|
||||
_("[FILENAME1] [FILENAME2]"),
|
||||
/* TRANSLATORS: command description */
|
||||
_("Emulate a device using a JSON manifest"),
|
||||
fu_util_device_emulate);
|
||||
fu_util_cmd_array_add(cmd_array,
|
||||
"inhibit",
|
||||
/* TRANSLATORS: command argument: uppercase, spaces->dashes */
|
||||
@ -4590,6 +4728,34 @@ main(int argc, char *argv[])
|
||||
/* TRANSLATORS: command description */
|
||||
_("Sets one or more BIOS settings"),
|
||||
fu_util_set_bios_setting);
|
||||
fu_util_cmd_array_add(cmd_array,
|
||||
"emulation-load",
|
||||
/* TRANSLATORS: command argument: uppercase, spaces->dashes */
|
||||
_("FILENAME"),
|
||||
/* TRANSLATORS: command description */
|
||||
_("Load device emulation data"),
|
||||
fu_util_emulation_load);
|
||||
fu_util_cmd_array_add(cmd_array,
|
||||
"emulation-save",
|
||||
/* TRANSLATORS: command argument: uppercase, spaces->dashes */
|
||||
_("FILENAME"),
|
||||
/* TRANSLATORS: command description */
|
||||
_("Save device emulation data"),
|
||||
fu_util_emulation_save);
|
||||
fu_util_cmd_array_add(cmd_array,
|
||||
"emulation-tag",
|
||||
/* TRANSLATORS: command argument: uppercase, spaces->dashes */
|
||||
_("[DEVICE-ID|GUID]"),
|
||||
/* TRANSLATORS: command description */
|
||||
_("Adds devices to watch for future emulation"),
|
||||
fu_util_emulation_tag);
|
||||
fu_util_cmd_array_add(cmd_array,
|
||||
"emulation-untag",
|
||||
/* TRANSLATORS: command argument: uppercase, spaces->dashes */
|
||||
_("[DEVICE-ID|GUID]"),
|
||||
/* TRANSLATORS: command description */
|
||||
_("Removes devices to watch for future emulation"),
|
||||
fu_util_emulation_untag);
|
||||
|
||||
/* do stuff on ctrl+c */
|
||||
priv->cancellable = g_cancellable_new();
|
||||
|
@ -957,6 +957,48 @@
|
||||
</doc:doc>
|
||||
</method>
|
||||
|
||||
<!--***********************************************************-->
|
||||
<method name='EmulationLoad'>
|
||||
<doc:doc>
|
||||
<doc:description>
|
||||
<doc:para>
|
||||
Load emulation data.
|
||||
</doc:para>
|
||||
</doc:description>
|
||||
</doc:doc>
|
||||
<arg type='ay' name='data' direction='in'>
|
||||
<doc:doc>
|
||||
<doc:summary>
|
||||
<doc:para>
|
||||
JSON data of each phase packaged as a ZIP archive
|
||||
(e.g. <doc:tt>setup.json</doc:tt>, <doc:tt>install.json</doc:tt>, <doc:tt>reload.json</doc:tt>).
|
||||
</doc:para>
|
||||
</doc:summary>
|
||||
</doc:doc>
|
||||
</arg>
|
||||
</method>
|
||||
|
||||
<!--***********************************************************-->
|
||||
<method name='EmulationSave'>
|
||||
<doc:doc>
|
||||
<doc:description>
|
||||
<doc:para>
|
||||
Return emulation data.
|
||||
</doc:para>
|
||||
</doc:description>
|
||||
</doc:doc>
|
||||
<arg type='ay' name='data' direction='out'>
|
||||
<doc:doc>
|
||||
<doc:summary>
|
||||
<doc:para>
|
||||
JSON data of each phase packaged as a compressed ZIP archive
|
||||
(e.g. <doc:tt>setup.json</doc:tt>, <doc:tt>install.json</doc:tt>, <doc:tt>reload.json</doc:tt>).
|
||||
</doc:para>
|
||||
</doc:summary>
|
||||
</doc:doc>
|
||||
</arg>
|
||||
</method>
|
||||
|
||||
<!--***********************************************************-->
|
||||
<signal name='Changed'>
|
||||
<doc:doc>
|
||||
|
@ -1,4 +1,4 @@
|
||||
[wrap-git]
|
||||
directory = gusb
|
||||
url = https://github.com/hughsie/libgusb.git
|
||||
revision = 0.4.0
|
||||
revision = 0.4.5
|
||||
|
Loading…
Reference in New Issue
Block a user