Skip to content

Commit 1a4e14c

Browse files
committed
Early GUI connection to preloaded disposables
When the GUI session is established before being paused, the preloaded disposables can use the same GUI session after requesting the qube, thus the time to connect to the session is offset from the request stage to the preload stage. Reverting initial iteration: 212b148 It isn't the default because it comes with various disadvantages: - Depends on apps not autostarting, else they may appear before the qube is paused and can cause confusion; - Cannot adapt to monitor changes, screen resize, plugging external monitor; - Cannot survive display manager logout+login; - Only works after using has logged in to GUI session; For: QubesOS/qubes-issues#10230 For: QubesOS/qubes-issues#1512
1 parent 6b978a3 commit 1a4e14c

10 files changed

Lines changed: 119 additions & 16 deletions

File tree

linux/autostart/Makefile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
all:
2+
true
3+
4+
install:
5+
mkdir -p $(DESTDIR)/etc/xdg/autostart
6+
cp qubes-preload-dispvm-gui.desktop $(DESTDIR)/etc/xdg/autostart
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[Desktop Entry]
2+
Icon=qubes
3+
Name=Qubes Preload Disposables
4+
Comment=Workaround for session monitoring with qubes.WaitForSession
5+
Categories=System;Monitor;
6+
Exec=systemctl start qubes-preload-dispvm-gui.service
7+
Terminal=false
8+
NoDisplay=true
9+
Type=Application

linux/aux-tools/preload-dispvm

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
"""
44
This script is outside of qubesd because it relies on systemd to:
55
6-
- Order this action after the autostart or standard qubes;
6+
- Order this action after the autostart of normal qubes;
77
- Skip preloading if kernel command line prevents autostart.
88
"""
99

10+
import argparse
1011
import asyncio
1112
import concurrent.futures
1213
import qubesadmin
@@ -23,6 +24,18 @@ def get_preload_max(qube) -> int | None:
2324

2425

2526
async def main():
27+
parser = argparse.ArgumentParser(
28+
description="Autostart preloaded disposable cycle"
29+
)
30+
parser.add_argument(
31+
"--gui", action="store_true", help="start qubes with GUI session"
32+
)
33+
args = parser.parse_args()
34+
35+
call_arg = "preload-autostart"
36+
if args.gui:
37+
call_arg += "+gui"
38+
2639
app = qubesadmin.Qubes()
2740
domains = app.domains
2841
default_dispvm = getattr(app, "default_dispvm", None)
@@ -54,7 +67,7 @@ async def main():
5467
maximum = get_preload_max(qube)
5568
msg = f"{qube}:{maximum}"
5669
print(repr(msg))
57-
exec_args = qube.qubesd_call, qube.name, method, "preload-autostart"
70+
exec_args = qube.qubesd_call, qube.name, method, call_arg
5871
future = loop.run_in_executor(executor, *exec_args)
5972
tasks.append(future)
6073
await asyncio.gather(*tasks)

linux/systemd/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ install:
1010
cp qubes-qmemman.service $(DESTDIR)$(UNITDIR)
1111
cp qubesd.service $(DESTDIR)$(UNITDIR)
1212
cp qubes-preload-dispvm.service $(DESTDIR)$(UNITDIR)
13+
cp qubes-preload-dispvm-gui.service $(DESTDIR)$(UNITDIR)
1314
install -d $(DESTDIR)$(UNITDIR)/lvm2-pvscan@.service.d
1415
install -m 0644 lvm2-pvscan@.service.d_30_qubes.conf \
1516
$(DESTDIR)$(UNITDIR)/lvm2-pvscan@.service.d/30_qubes.conf
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[Unit]
2+
Description=Preload Qubes' Disposables after DISPLAY is available
3+
ConditionKernelCommandLine=!qubes.skip_autostart
4+
# After qmemman so the daemon can create the file containing available memory.
5+
After=qubesd.service qubes-meminfo-writer-dom0.service
6+
7+
[Service]
8+
Type=oneshot
9+
ExecStart=/usr/lib/qubes/preload-dispvm --gui
10+
Group=qubes
11+
RemainAfterExit=yes
12+
13+
[Install]
14+
WantedBy=multi-user.target

linux/systemd/qubes-preload-dispvm.service

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[Unit]
2-
Description=Preload Qubes DispVMs
2+
Description=Preload Qubes' Disposables
33
ConditionKernelCommandLine=!qubes.skip_autostart
44
# After qmemman so the daemon can create the file containing available memory.
55
After=qubesd.service qubes-meminfo-writer-dom0.service

qubes/api/admin.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1308,13 +1308,20 @@ async def _vm_create(
13081308
@qubes.api.method("admin.vm.CreateDisposable", scope="global", write=True)
13091309
async def create_disposable(self, untrusted_payload):
13101310
"""
1311-
Create a disposable. If the RPC argument is ``preload-autostart``,
1312-
cleanse the preload list and start preloading fresh disposables.
1311+
Create a disposable. If the RPC argument starts with
1312+
``preload-autostart``, cleanse the preload list and start preloading
1313+
fresh disposables.
13131314
"""
1314-
self.enforce(self.arg in [None, "", "preload-autostart"])
1315+
self.enforce(
1316+
self.arg in [None, "", "preload-autostart", "preload-autostart+gui"]
1317+
)
13151318
preload_autostart = False
1319+
preload_autostart_gui = False
13161320
if self.arg == "preload-autostart":
13171321
preload_autostart = True
1322+
elif self.arg == "preload-autostart+gui":
1323+
preload_autostart = True
1324+
preload_autostart_gui = True
13181325
if untrusted_payload not in (b"", b"uuid"):
13191326
raise qubes.exc.QubesValueError(
13201327
"Invalid payload for admin.vm.CreateDisposable: "
@@ -1327,9 +1334,15 @@ async def create_disposable(self, untrusted_payload):
13271334
appvm = self.dest
13281335

13291336
self.fire_event_for_permission(dispvm_template=appvm)
1337+
1338+
preload_autostart_event = None
13301339
if preload_autostart:
1331-
await appvm.fire_event_async("domain-preload-dispvm-autostart")
1340+
preload_autostart_event = "domain-preload-dispvm-autostart"
1341+
if preload_autostart_gui:
1342+
preload_autostart_event += "-gui"
1343+
await appvm.fire_event_async(preload_autostart_event)
13321344
return
1345+
13331346
dispvm = await qubes.vm.dispvm.DispVM.from_appvm(appvm)
13341347
# TODO: move this to extension (in race-free fashion, better than here)
13351348
dispvm.tags.add("created-by-" + str(self.src))

qubes/vm/dispvm.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ class DispVM(qubes.vm.qubesvm.QubesVM):
287287
}
288288

289289
def __init__(self, app, xml, *args, **kwargs) -> None:
290-
assert isinstance(self, qubes.vm.BaseVM)
290+
assert isinstance(self, qubes.vm.qubesvm.QubesVM)
291291
self.volume_config = copy.deepcopy(self.default_volume_config)
292292
template = kwargs.get("template", None)
293293
self.preload_complete = asyncio.Event()
@@ -370,6 +370,7 @@ def __init__(self, app, xml, *args, **kwargs) -> None:
370370
(key, value)
371371
for key, value in template.features.items()
372372
if not key.startswith("preload-dispvm")
373+
and key != "preload-dispvm-early-gui"
373374
]
374375
)
375376
self.tags.update(template.tags)
@@ -440,7 +441,7 @@ async def on_domain_started_dispvm(
440441
return
441442
timeout = self.qrexec_timeout
442443
# https://github.com/QubesOS/qubes-issues/issues/9964
443-
rpc = "qubes.WaitForRunningSystem"
444+
rpc = self.get_preload_service()
444445
path = "/run/qubes-rpc:/usr/local/etc/qubes-rpc:/etc/qubes-rpc"
445446
service = '$(PATH="' + path + '" command -v ' + rpc + ")"
446447
try:
@@ -844,3 +845,23 @@ async def start(self, **kwargs):
844845
def create_qdb_entries(self) -> None:
845846
super().create_qdb_entries()
846847
self.untrusted_qdb.write("/qubes-vm-persistence", "none")
848+
849+
@classmethod
850+
def get_preload_service(cls) -> str:
851+
"""
852+
Check which service is requires to check if system is ready.
853+
854+
:rtype: str
855+
"""
856+
# pylint: disable=unused-argument
857+
assert isinstance(cls, qubes.vm.qubesvm.QubesVM)
858+
# pylint: disable=no-member
859+
if (
860+
cls.features.get("preload-dispvm-early-gui", None)
861+
and cls.guivm
862+
and cls.features.get("gui", True)
863+
):
864+
service = "qubes.WaitForSession"
865+
else:
866+
service = "qubes.WaitForRunningSystem"
867+
return service

qubes/vm/mix/dvmtemplate.py

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ def on_domain_loaded(self, event) -> None:
6363
# pylint: disable=unused-argument
6464
assert isinstance(self, qubes.vm.BaseVM)
6565
changes = False
66-
# Preloading began and host rebooted and autostart event didn't run yet.
66+
# Began preloading, host rebooted and autostart event didn't run since.
6767
old_preload = self.get_feat_preload()
6868
clean_preload = old_preload.copy()
6969
for unwanted_disp in old_preload:
@@ -180,7 +180,7 @@ def on_feature_pre_set_preload_dispvm_max(
180180
if not self.features.check_with_template("qrexec", None):
181181
raise qubes.exc.QubesValueError("Qube does not support qrexec")
182182

183-
service = "qubes.WaitForRunningSystem"
183+
service = self.get_preload_service()
184184
if not self.supports_preload():
185185
raise qubes.exc.QubesValueError(
186186
"Qube does not support the RPC '%s'" % service
@@ -412,6 +412,7 @@ def __on_property_set_template(
412412
@qubes.events.handler(
413413
"domain-preload-dispvm-used",
414414
"domain-preload-dispvm-autostart",
415+
"domain-preload-dispvm-autostart-gui",
415416
"domain-preload-dispvm-start",
416417
)
417418
async def on_domain_preload_dispvm_used(
@@ -445,16 +446,30 @@ async def on_domain_preload_dispvm_used(
445446
if delay:
446447
event_log += " with a delay of %s second(s)" % f"{delay:.1f}"
447448
self.log.info(event_log)
448-
service = "qubes.WaitForRunningSystem"
449+
449450
if not self.supports_preload():
450451
raise qubes.exc.QubesValueError(
451452
"Qube does not support the RPC '%s' but tried to preload, "
452-
"check if template is outdated" % service
453+
"check if template is outdated" % self.get_preload_service()
453454
)
455+
if event.startswith("autostart"):
456+
early_gui = self.features.get("preload-dispvm-early-gui", None)
457+
if event == "autostart" and early_gui:
458+
self.log.info(
459+
"Skipping preload autostart as early GUI was requested"
460+
)
461+
return
462+
if event == "autostart-gui" and not early_gui:
463+
self.log.info(
464+
"Skipping early GUI preload autostart as it was not "
465+
"configured"
466+
)
467+
return
468+
454469
if delay:
455470
await asyncio.sleep(delay)
456471

457-
if event == "autostart":
472+
if event.startswith("autostart"):
458473
self.remove_preload_excess(0, reason="event autostart was called")
459474
elif not self.can_preload():
460475
self.remove_preload_excess(reason="there may be absent qubes")
@@ -672,15 +687,23 @@ def remove_preload_excess(
672687
dispvm = self.app.domains[unwanted_disp]
673688
asyncio.ensure_future(dispvm.cleanup())
674689

690+
def get_preload_service(self) -> str:
691+
"""
692+
Check which service is requires to check if system is ready.
693+
694+
:rtype: str
695+
"""
696+
# pylint: disable=too-many-function-args
697+
return qubes.vm.dispvm.DispVM.get_preload_service(self)
698+
675699
def supports_preload(self) -> bool:
676700
"""
677701
Check if the necessary RPC is supported.
678702
679703
:rtype: bool
680704
"""
681705
assert isinstance(self, qubes.vm.BaseVM)
682-
service = "qubes.WaitForRunningSystem"
683-
supported_service = "supported-rpc." + service
706+
supported_service = "supported-rpc." + self.get_preload_service()
684707
if self.features.check_with_template(supported_service, False):
685708
return True
686709
return False

rpm_spec/core-dom0.spec.in

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,8 @@ done
378378

379379
%{_mandir}/man1/qubes*.1*
380380

381+
%{_sysconfdir}/xdg/autostart/qubes-preload-dispvm-gui.desktop
382+
381383
%dir %{python3_sitelib}/qubes-*.egg-info
382384
%{python3_sitelib}/qubes-*.egg-info/*
383385

@@ -578,6 +580,7 @@ done
578580
%{_unitdir}/qubes-vm@.service
579581
%{_unitdir}/qubesd.service
580582
%{_unitdir}/qubes-preload-dispvm.service
583+
%{_unitdir}/qubes-preload-dispvm-gui.service
581584
%attr(2770,root,qubes) %dir /var/lib/qubes
582585
%attr(2770,root,qubes) %dir /var/lib/qubes/vm-templates
583586
%attr(2770,root,qubes) %dir /var/lib/qubes/appvms

0 commit comments

Comments
 (0)