Add support for emulating and recording devices

Based on patches from Frédéric Danis <frederic.danis@collabora.com>
This commit is contained in:
Richard Hughes 2023-01-25 13:41:53 +00:00 committed by Mario Limonciello
parent 8bcb66745a
commit b59c82e1bd
50 changed files with 1241 additions and 178 deletions

View File

@ -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

View File

@ -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'

View File

@ -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=

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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"
]

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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"
]

View File

@ -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",

View File

@ -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",

View File

@ -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

View File

@ -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

View File

@ -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,
)

View File

@ -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

View File

@ -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
View File

@ -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
View 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!

View File

@ -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>

View File

@ -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);
}

View File

@ -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

View File

@ -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 */

View File

@ -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);

View File

@ -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;
}

View File

@ -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:
*

View File

@ -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;

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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)
{

View File

@ -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 *

View File

@ -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;

View File

@ -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;

View File

@ -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);

View File

@ -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);

View File

@ -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

View File

@ -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);

View File

@ -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,

View File

@ -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;

View File

@ -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();

View File

@ -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>

View File

@ -1,4 +1,4 @@
[wrap-git]
directory = gusb
url = https://github.com/hughsie/libgusb.git
revision = 0.4.0
revision = 0.4.5