Skip to content

Commit 2d64eae

Browse files
committed
add custom_persist extension
1 parent c1cdf38 commit 2d64eae

4 files changed

Lines changed: 105 additions & 0 deletions

File tree

doc/qubes-ext.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Extensions defined here
1414
.. autoclass:: qubes.ext.admin.AdminExtension
1515
.. autoclass:: qubes.ext.block.BlockDeviceExtension
1616
.. autoclass:: qubes.ext.core_features.CoreFeatures
17+
.. autoclass:: qubes.ext.custom_persist.CustomPersist
1718
.. autoclass:: qubes.ext.gui.GUI
1819
.. autoclass:: qubes.ext.pci.PCIDeviceExtension
1920
.. autoclass:: qubes.ext.r3compatibility.R3Compatibility

qubes/ext/custom_persist.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# -*- encoding: utf-8 -*-
2+
#
3+
# The Qubes OS Project, http://www.qubes-os.org
4+
#
5+
# Copyright (C) 2024 Guillaume Chinal <guiiix@invisiblethingslab.com>
6+
#
7+
# This library is free software; you can redistribute it and/or
8+
# modify it under the terms of the GNU Lesser General Public
9+
# License as published by the Free Software Foundation; either
10+
# version 2.1 of the License, or (at your option) any later version.
11+
#
12+
# This library is distributed in the hope that it will be useful,
13+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15+
# Lesser General Public License for more details.
16+
#
17+
# You should have received a copy of the GNU Lesser General Public
18+
# License along with this library; if not, see <https://www.gnu.org/licenses/>.
19+
20+
import qubes.ext
21+
import qubes.config
22+
23+
FEATURE_PREFIX = "custom-persist."
24+
QDB_PREFIX = "/persist/"
25+
QDB_KEY_LIMIT = 63
26+
27+
28+
class CustomPersist(qubes.ext.Extension):
29+
"""This extension allows to create minimal-state APP with by configuring an
30+
exhaustive list of bind dirs(and files)
31+
"""
32+
33+
@staticmethod
34+
def _extract_key_from_feature(feature) -> str:
35+
return feature[len(FEATURE_PREFIX) :]
36+
37+
@staticmethod
38+
def _is_expected_feature(feature) -> bool:
39+
return feature.startswith(FEATURE_PREFIX)
40+
41+
@staticmethod
42+
def _is_valid_key(key, vm) -> bool:
43+
if not key:
44+
vm.log.warning(
45+
"Got empty custom-persist key, ignoring: {}".format(key)
46+
)
47+
return False
48+
49+
# QubesDB key length limit
50+
key_maxlen = QDB_KEY_LIMIT - len(QDB_PREFIX)
51+
if len(key) >= key_maxlen:
52+
vm.log.warning(
53+
"custom-persist key is too long (max {}), ignoring: "
54+
"{}".format(key_maxlen, key)
55+
)
56+
return False
57+
return True
58+
59+
def _write_db_value(self, feature, value, vm):
60+
vm.untrusted_qdb.write(
61+
"{}{}".format(QDB_PREFIX, self._extract_key_from_feature(feature)),
62+
str(value),
63+
)
64+
65+
@qubes.ext.handler("domain-qdb-create")
66+
def on_domain_qdb_create(self, vm, event):
67+
"""Actually export features"""
68+
# pylint: disable=unused-argument
69+
for feature, value in vm.features.items():
70+
if self._is_expected_feature(feature) and self._is_valid_key(
71+
self._extract_key_from_feature(feature), vm
72+
):
73+
self._write_db_value(feature, value, vm)
74+
75+
@qubes.ext.handler("domain-feature-set:*")
76+
def on_domain_feature_set(self, vm, event, feature, value, oldvalue=None):
77+
"""Inject persist keys in QubesDB in runtime"""
78+
# pylint: disable=unused-argument
79+
80+
if not self._is_expected_feature(feature):
81+
return
82+
83+
if not self._is_valid_key(self._extract_key_from_feature(feature), vm):
84+
return
85+
86+
if not vm.is_running():
87+
return
88+
89+
self._write_db_value(feature, value, vm)
90+
91+
@qubes.ext.handler("domain-feature-delete:*")
92+
def on_domain_feature_delete(self, vm, event, feature):
93+
"""Update /vm-config/ QubesDB tree in runtime"""
94+
# pylint: disable=unused-argument
95+
if not vm.is_running():
96+
return
97+
if not feature.startswith(FEATURE_PREFIX):
98+
return
99+
100+
vm.untrusted_qdb.rm(
101+
"{}{}".format(QDB_PREFIX, self._extract_key_from_feature(feature))
102+
)

rpm_spec/core-dom0.spec.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,7 @@ done
443443
%{python3_sitelib}/qubes/ext/backup_restore.py
444444
%{python3_sitelib}/qubes/ext/block.py
445445
%{python3_sitelib}/qubes/ext/core_features.py
446+
%{python3_sitelib}/qubes/ext/custom_persist.py
446447
%{python3_sitelib}/qubes/ext/gui.py
447448
%{python3_sitelib}/qubes/ext/audio.py
448449
%{python3_sitelib}/qubes/ext/pci.py

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ def run(self):
6464
'qubes.ext.backup_restore = '
6565
'qubes.ext.backup_restore:BackupRestoreExtension',
6666
'qubes.ext.core_features = qubes.ext.core_features:CoreFeatures',
67+
'qubes.ext.custom_persist = qubes.ext.custom_persist:CustomPersist',
6768
'qubes.ext.gui = qubes.ext.gui:GUI',
6869
'qubes.ext.audio = qubes.ext.audio:AUDIO',
6970
'qubes.ext.r3compatibility = qubes.ext.r3compatibility:R3Compatibility',

0 commit comments

Comments
 (0)