core3: some properties can be set only once

Some properties should not be changed by user at will (like UUID). The
solution is to make them write-once, so they will be set when loading
from XML and frozen for the lifespan of the object holding the property.
When desperately needed, users may edit XML by hand.

fixes QubesOS/qubes-issues#1235
This commit is contained in:
Wojtek Porczyk 2015-09-25 21:36:35 +02:00
parent a017d78174
commit b4d51b016b
4 changed files with 50 additions and 13 deletions

View File

@ -585,7 +585,8 @@ class property(object): # pylint: disable=redefined-builtin,invalid-name
_NO_DEFAULT = object()
def __init__(self, name, setter=None, saver=None, type=None,
default=_NO_DEFAULT, load_stage=2, order=0, save_via_ref=False,
default=_NO_DEFAULT, write_once=False, load_stage=2, order=0,
save_via_ref=False,
ls_head=None, ls_width=None, doc=None):
# pylint: disable=redefined-builtin
self.__name__ = name
@ -594,6 +595,7 @@ class property(object): # pylint: disable=redefined-builtin,invalid-name
lambda self, prop, value: str(value))
self._type = type
self._default = default
self._write_once = write_once
self.order = order
self.load_stage = load_stage
self.save_via_ref = save_via_ref
@ -628,6 +630,8 @@ class property(object): # pylint: disable=redefined-builtin,invalid-name
def __set__(self, instance, value):
self._enforce_write_once(instance)
if value is self.__class__.DEFAULT:
self.__delete__(instance)
return
@ -661,6 +665,8 @@ class property(object): # pylint: disable=redefined-builtin,invalid-name
def __delete__(self, instance):
self._enforce_write_once(instance)
try:
oldvalue = getattr(instance, self.__name__)
has_oldvalue = True
@ -700,6 +706,13 @@ class property(object): # pylint: disable=redefined-builtin,invalid-name
return self.__name__ == other.__name__
def _enforce_write_once(self, instance):
if self._write_once and not instance.property_is_default(self):
raise AttributeError(
'property {!r} is write-once and already set'.format(
self.__name__))
#
# exceptions
#
@ -806,17 +819,20 @@ class PropertyHolder(qubes.events.Emitter):
'''
def __init__(self, xml, **kwargs):
super(PropertyHolder, self).__init__()
self.xml = xml
for key, value in kwargs.items():
setattr(self, key, value)
propvalues = {}
all_names = set(prop.__name__ for prop in self.property_list())
for key in list(kwargs.keys()):
if not key in all_names:
continue
setattr(self, key, kwargs.pop(key))
propvalues[key] = kwargs.pop(key)
super(PropertyHolder, self).__init__(**kwargs)
for key, value in propvalues.items():
setattr(self, key, value)
@classmethod

View File

@ -134,7 +134,7 @@ class TC_10_property(qubes.tests.QubesTestCase):
self.assertEqual(holder.testprop1, 5)
self.assertNotEqual(holder.testprop1, '5')
def test_090_delete(self):
def test_080_delete(self):
self.holder.testprop1 = 'testvalue'
try:
if self.holder.testprop1 != 'testvalue':
@ -147,7 +147,7 @@ class TC_10_property(qubes.tests.QubesTestCase):
with self.assertRaises(AttributeError):
self.holder.testprop1
def test_090_delete_by_assign(self):
def test_081_delete_by_assign(self):
self.holder.testprop1 = 'testvalue'
try:
if self.holder.testprop1 != 'testvalue':
@ -160,7 +160,7 @@ class TC_10_property(qubes.tests.QubesTestCase):
with self.assertRaises(AttributeError):
self.holder.testprop1
def test_092_delete_default(self):
def test_082_delete_default(self):
class MyTestHolder(qubes.tests.TestEmitter, qubes.PropertyHolder):
testprop1 = qubes.property('testprop1', default='defaultvalue')
holder = MyTestHolder(None)
@ -176,6 +176,26 @@ class TC_10_property(qubes.tests.QubesTestCase):
self.assertEqual(holder.testprop1, 'defaultvalue')
def test_090_write_once_set(self):
class MyTestHolder(qubes.tests.TestEmitter, qubes.PropertyHolder):
testprop1 = qubes.property('testprop1', write_once=True)
holder = MyTestHolder(None)
holder.testprop1 = 'testvalue'
with self.assertRaises(AttributeError):
holder.testprop1 = 'testvalue2'
def test_091_write_once_delete(self):
class MyTestHolder(qubes.tests.TestEmitter, qubes.PropertyHolder):
testprop1 = qubes.property('testprop1', write_once=True)
holder = MyTestHolder(None)
holder.testprop1 = 'testvalue'
with self.assertRaises(AttributeError):
del holder.testprop1
class TestHolder(qubes.tests.TestEmitter, qubes.PropertyHolder):
testprop1 = qubes.property('testprop1', order=0)

View File

@ -59,15 +59,16 @@ class TC_00_Column(qubes.tests.QubesTestCase):
class TC_90_globals(qubes.tests.QubesTestCase):
# @qubes.tests.skipUnlessDom0
def test_100_simple_flag(self):
flag = qubes.tools.qvm_ls.simple_flag(1, 'T', 'qid')
flag = qubes.tools.qvm_ls.simple_flag(1, 'T', 'internal')
# TODO after serious testing of QubesVM and Qubes app, this should be
# using normal components
app = qubes.tests.vm.adminvm.TestApp()
vm = qubes.vm.adminvm.AdminVM(app, None, qid=0, name='dom0')
vm = qubes.vm.adminvm.AdminVM(app, None,
qid=0, name='dom0', internal='False')
self.assertFalse(flag(None, vm))
vm.qid = 1
vm.internal = 'True'
self.assertTrue(flag(None, vm))

View File

@ -154,7 +154,7 @@ class QubesVM(qubes.vm.BaseVM):
# type=bool, setter=qubes.property.bool,
# doc='`True` if it is NetVM or ProxyVM, false otherwise.')
qid = qubes.property('qid', type=int,
qid = qubes.property('qid', type=int, write_once=True,
setter=_setter_qid,
ls_width=3,
doc='''Internal, persistent identificator of particular domain. Note
@ -164,7 +164,7 @@ class QubesVM(qubes.vm.BaseVM):
ls_width=31,
doc='User-specified name of the domain.')
uuid = qubes.property('uuid', type=uuid.UUID,
uuid = qubes.property('uuid', type=uuid.UUID, write_once=True,
ls_width=36,
doc='UUID from libvirt.')