core3 move: storage/*

This commit is contained in:
Wojtek Porczyk 2015-01-16 15:33:03 +01:00
parent 0dbcdb8c0d
commit 8afba4c5e9
7 changed files with 478 additions and 421 deletions

View File

@ -1,201 +0,0 @@
#!/usr/bin/python2
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2013 Marek Marczykowski <marmarek@invisiblethingslab.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
#
from __future__ import absolute_import
import os
import os.path
import re
import shutil
import subprocess
import sys
from qubes.qubes import vm_files,system_path,defaults
from qubes.qubes import QubesException
import qubes.qubesutils
class QubesVmStorage(object):
"""
Class for handling VM virtual disks. This is base class for all other
implementations, mostly with Xen on Linux in mind.
"""
def __init__(self, vm,
private_img_size = None,
root_img_size = None,
modules_img = None,
modules_img_rw = False):
self.vm = vm
self.vmdir = vm.dir_path
if private_img_size:
self.private_img_size = private_img_size
else:
self.private_img_size = defaults['private_img_size']
if root_img_size:
self.root_img_size = root_img_size
else:
self.root_img_size = defaults['root_img_size']
self.private_img = os.path.join(self.vmdir, vm_files["private_img"])
if self.vm.template:
self.root_img = self.vm.template.root_img
else:
self.root_img = os.path.join(self.vmdir, vm_files["root_img"])
self.volatile_img = os.path.join(self.vmdir, vm_files["volatile_img"])
# For now compute this path still in QubesVm
self.modules_img = modules_img
self.modules_img_rw = modules_img_rw
# Additional drive (currently used only by HVM)
self.drive = None
def get_config_params(self):
raise NotImplementedError
def _copy_file(self, source, destination):
"""
Effective file copy, preserving sparse files etc.
"""
# TODO: Windows support
# We prefer to use Linux's cp, because it nicely handles sparse files
retcode = subprocess.call (["cp", source, destination])
if retcode != 0:
raise IOError ("Error while copying {0} to {1}".\
format(source, destination))
def get_disk_utilization(self):
return qubes.qubesutils.get_disk_usage(self.vmdir)
def get_disk_utilization_private_img(self):
return qubes.qubesutils.get_disk_usage(self.private_img)
def get_private_img_sz(self):
if not os.path.exists(self.private_img):
return 0
return os.path.getsize(self.private_img)
def resize_private_img(self, size):
raise NotImplementedError
def create_on_disk_private_img(self, verbose, source_template = None):
raise NotImplementedError
def create_on_disk_root_img(self, verbose, source_template = None):
raise NotImplementedError
def create_on_disk(self, verbose, source_template = None):
if source_template is None:
source_template = self.vm.template
old_umask = os.umask(002)
if verbose:
print >> sys.stderr, "--> Creating directory: {0}".format(self.vmdir)
os.mkdir (self.vmdir)
self.create_on_disk_private_img(verbose, source_template)
self.create_on_disk_root_img(verbose, source_template)
self.reset_volatile_storage(verbose, source_template)
os.umask(old_umask)
def clone_disk_files(self, src_vm, verbose):
if verbose:
print >> sys.stderr, "--> Creating directory: {0}".format(self.vmdir)
os.mkdir (self.vmdir)
if src_vm.private_img is not None and self.private_img is not None:
if verbose:
print >> sys.stderr, "--> Copying the private image:\n{0} ==>\n{1}".\
format(src_vm.private_img, self.private_img)
self._copy_file(src_vm.private_img, self.private_img)
if src_vm.updateable and src_vm.root_img is not None and self.root_img is not None:
if verbose:
print >> sys.stderr, "--> Copying the root image:\n{0} ==>\n{1}".\
format(src_vm.root_img, self.root_img)
self._copy_file(src_vm.root_img, self.root_img)
# TODO: modules?
def rename(self, old_name, new_name):
old_vmdir = self.vmdir
new_vmdir = os.path.join(os.path.dirname(self.vmdir), new_name)
os.rename(self.vmdir, new_vmdir)
self.vmdir = new_vmdir
if self.private_img:
self.private_img = self.private_img.replace(old_vmdir, new_vmdir)
if self.root_img:
self.root_img = self.root_img.replace(old_vmdir, new_vmdir)
if self.volatile_img:
self.volatile_img = self.volatile_img.replace(old_vmdir, new_vmdir)
def verify_files(self):
if not os.path.exists (self.vmdir):
raise QubesException (
"VM directory doesn't exist: {0}".\
format(self.vmdir))
if self.root_img and not os.path.exists (self.root_img):
raise QubesException (
"VM root image file doesn't exist: {0}".\
format(self.root_img))
if self.private_img and not os.path.exists (self.private_img):
raise QubesException (
"VM private image file doesn't exist: {0}".\
format(self.private_img))
if self.modules_img is not None:
if not os.path.exists(self.modules_img):
raise QubesException (
"VM kernel modules image does not exists: {0}".\
format(self.modules_img))
def remove_from_disk(self):
shutil.rmtree (self.vmdir)
def reset_volatile_storage(self, verbose = False, source_template = None):
if source_template is None:
source_template = self.vm.template
# Re-create only for template based VMs
if source_template is not None and self.volatile_img:
if os.path.exists(self.volatile_img):
os.remove(self.volatile_img)
# For StandaloneVM create it only if not already exists (eg after backup-restore)
if self.volatile_img and not os.path.exists(self.volatile_img):
if verbose:
print >> sys.stderr, "--> Creating volatile image: {0}...".\
format(self.volatile_img)
subprocess.check_call([system_path["prepare_volatile_img_cmd"],
self.volatile_img, str(self.root_img_size / 1024 / 1024)])
def prepare_for_vm_startup(self, verbose):
self.reset_volatile_storage(verbose=verbose)
if self.private_img and not os.path.exists (self.private_img):
print >>sys.stderr, "WARNING: Creating empty VM private image file: {0}".\
format(self.private_img)
self.storage.create_on_disk_private_img(verbose=False)

View File

@ -1,185 +0,0 @@
#!/usr/bin/python2
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2013 Marek Marczykowski <marmarek@invisiblethingslab.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
#
from __future__ import absolute_import
import os
import os.path
import subprocess
import sys
import re
from qubes.storage import QubesVmStorage
from qubes.qubes import QubesException, vm_files
class QubesXenVmStorage(QubesVmStorage):
"""
Class for VM storage of Xen VMs.
"""
def __init__(self, vm, **kwargs):
super(QubesXenVmStorage, self).__init__(vm, **kwargs)
self.root_dev = "xvda"
self.private_dev = "xvdb"
self.volatile_dev = "xvdc"
self.modules_dev = "xvdd"
if self.vm.is_template():
self.rootcow_img = os.path.join(self.vmdir, vm_files["rootcow_img"])
else:
self.rootcow_img = None
def _format_disk_dev(self, path, script, vdev, rw=True, type="disk", domain=None):
if path is None:
return ''
template = " <disk type='block' device='{type}'>\n" \
" <driver name='phy'/>\n" \
" <source dev='{path}'/>\n" \
" <target dev='{vdev}' bus='xen'/>\n" \
"{params}" \
" </disk>\n"
params = ""
if not rw:
params += " <readonly/>\n"
if domain:
params += " <backenddomain name='%s'/>\n" % domain
if script:
params += " <script path='%s'/>\n" % script
return template.format(path=path, vdev=vdev, type=type,
params=params)
def _get_rootdev(self):
if self.vm.is_template() and \
os.path.exists(os.path.join(self.vmdir, "root-cow.img")):
return self._format_disk_dev(
"{dir}/root.img:{dir}/root-cow.img".format(
dir=self.vmdir),
"block-origin", self.root_dev, True)
elif self.vm.template:
return self._format_disk_dev(
"{dir}/root.img:{dir}/root-cow.img".format(
dir=self.vm.template.dir_path),
"block-snapshot", self.root_dev, False)
else:
return self._format_disk_dev(
"{dir}/root.img".format(dir=self.vmdir),
None, self.root_dev, True)
def get_config_params(self):
args = {}
args['rootdev'] = self._get_rootdev()
args['privatedev'] = \
self._format_disk_dev(self.private_img,
None, self.private_dev, True)
args['volatiledev'] = \
self._format_disk_dev(self.volatile_img,
None, self.volatile_dev, True)
if self.modules_img is not None:
args['otherdevs'] = \
self._format_disk_dev(self.modules_img,
None, self.modules_dev, self.modules_img_rw)
elif self.drive is not None:
(drive_type, drive_domain, drive_path) = self.drive.split(":")
if drive_domain.lower() == "dom0":
drive_domain = None
args['otherdevs'] = self._format_disk_dev(drive_path, None,
self.modules_dev,
rw=True if drive_type == "disk" else False, type=drive_type,
domain=drive_domain)
else:
args['otherdevs'] = ''
return args
def create_on_disk_private_img(self, verbose, source_template = None):
if source_template:
template_priv = source_template.private_img
if verbose:
print >> sys.stderr, "--> Copying the template's private image: {0}".\
format(template_priv)
self._copy_file(template_priv, self.private_img)
else:
f_private = open (self.private_img, "a+b")
f_private.truncate (self.private_img_size)
f_private.close ()
def create_on_disk_root_img(self, verbose, source_template = None):
if source_template:
if not self.vm.updateable:
# just use template's disk
return
else:
template_root = source_template.root_img
if verbose:
print >> sys.stderr, "--> Copying the template's root image: {0}".\
format(template_root)
self._copy_file(template_root, self.root_img)
else:
f_root = open (self.root_img, "a+b")
f_root.truncate (self.root_img_size)
f_root.close ()
if self.vm.is_template():
self.commit_template_changes()
def rename(self, old_name, new_name):
super(QubesXenVmStorage, self).rename(old_name, new_name)
old_dirpath = os.path.join(os.path.dirname(self.vmdir), old_name)
if self.rootcow_img:
self.rootcow_img = self.rootcow_img.replace(old_dirpath,
self.vmdir)
def resize_private_img(self, size):
f_private = open (self.private_img, "a+b")
f_private.truncate (size)
f_private.close ()
# find loop device if any
p = subprocess.Popen (["sudo", "losetup", "--associated", self.private_img],
stdout=subprocess.PIPE)
result = p.communicate()
m = re.match(r"^(/dev/loop\d+):\s", result[0])
if m is not None:
loop_dev = m.group(1)
# resize loop device
subprocess.check_call(["sudo", "losetup", "--set-capacity", loop_dev])
def commit_template_changes(self):
assert self.vm.is_template()
# TODO: move rootcow_img to this class; the same for vm.is_outdated()
if os.path.exists (self.rootcow_img):
os.rename (self.rootcow_img, self.rootcow_img + '.old')
old_umask = os.umask(002)
f_cow = open (self.rootcow_img, "w")
f_root = open (self.root_img, "r")
f_root.seek(0, os.SEEK_END)
f_cow.truncate (f_root.tell()) # make empty sparse file of the same size as root.img
f_cow.close ()
f_root.close()
os.umask(old_umask)

View File

@ -14,6 +14,7 @@ endif
mkdir -p $(DESTDIR)$(PYTHON_QUBESPATH)
mkdir \
$(DESTDIR)$(PYTHON_QUBESPATH)/vm \
$(DESTDIR)$(PYTHON_QUBESPATH)/storage \
$(DESTDIR)$(PYTHON_QUBESPATH)/ext \
$(DESTDIR)$(PYTHON_QUBESPATH)/tests \
$(DESTDIR)$(PYTHON_QUBESPATH)/tests/vm
@ -43,6 +44,11 @@ endif
vm/templatevm.py* \
$(DESTDIR)$(PYTHON_QUBESPATH)/vm
cp \
storage/__init__.py* \
storage/xen.py* \
$(DESTDIR)$(PYTHON_QUBESPATH)/storage
cp ext/__init__.py* $(DESTDIR)$(PYTHON_QUBESPATH)/ext
cp \

245
qubes/storage/__init__.py Normal file
View File

@ -0,0 +1,245 @@
#!/usr/bin/python2
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2013 Marek Marczykowski <marmarek@invisiblethingslab.com>
# Copyright (C) 2015 Wojtek Porczyk <woju@invisiblethingslab.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
from __future__ import absolute_import
import os
import os.path
import re
import shutil
import subprocess
import sys
import qubes
import qubes.utils
class VMStorage(object):
'''Class for handling VM virtual disks.
This is base class for all other implementations, mostly with Xen on Linux
in mind.
'''
def __init__(self, vm, private_img_size=None, root_img_size=None,
modules_img=None, modules_img_rw=False):
#: Domain for which we manage storage
self.vm = vm
#: Size of the private image
self.private_img_size = private_img_size \
if private_img_size is not None \
else qubes.config.defaults['private_img_size']
#: Size of the root image
self.root_img_size = root_img_size \
if root_img_size is not None \
else qubes.config.defaults['root_img_size']
# For now compute this path still in QubesVm
self.modules_img = modules_img
self.modules_img_rw = modules_img_rw
#: Additional drive (currently used only by HVM)
self.drive = None
@property
def private_img(self):
'''Path to the private image'''
return self.abspath(qubes.config.vm_files['private_img'])
@property
def root_img(self):
'''Path to the root image'''
return self.vm.template.root_img if hasattr(self.vm, 'template') \
else self.abspath(qubes.config.vm_files['root_img'])
@property
def volatile_img(self):
'''Path to the volatile image'''
return self.abspath(qubes.config.vm_files['volatile_img'])
def abspath(self, path, rel=None):
'''Make absolute path.
If given path is relative, it is interpreted as relative to
:py:attr:`self.vm.dir_path` or given *rel*.
'''
return path if os.path.isabs(path) \
else os.path.join(rel or self.vm.dir_path, path)
def get_config_params(self):
raise NotImplementedError()
def _copy_file(self, source, destination):
'''Effective file copy, preserving sparse files etc.
'''
# TODO: Windows support
# We prefer to use Linux's cp, because it nicely handles sparse files
try:
subprocess.check_call(['cp', source, destination])
except subprocess.CalledProcessError:
raise IOError('Error while copying {!r} to {!r}'.format(
source, destination))
def get_disk_utilization(self):
return qubes.utils.get_disk_usage(self.vmdir)
def get_disk_utilization_private_img(self):
return qubes.utils.get_disk_usage(self.private_img)
def get_private_img_sz(self):
if not os.path.exists(self.private_img):
return 0
return os.path.getsize(self.private_img)
def resize_private_img(self, size):
raise NotImplementedError()
def create_on_disk_private_img(self, source_template=None):
raise NotImplementedError()
def create_on_disk_root_img(self, source_template=None):
raise NotImplementedError()
def create_on_disk(self, source_template=None):
if source_template is None:
source_template = self.vm.template
old_umask = os.umask(002)
self.vm.log.info('Creating directory: {0}'.format(self.vm.dir_path))
os.mkdir(self.vmdir)
self.create_on_disk_private_img(verbose, source_template)
self.create_on_disk_root_img(verbose, source_template)
self.reset_volatile_storage(verbose, source_template)
os.umask(old_umask)
def clone_disk_files(self, src_vm):
self.vm.log.info('Creating directory: {0}'.format(self.vm.dir_path))
os.mkdir(self.vm.dir_path)
if hasattr(src_vm, 'private_img'):
self.vm.log.info('Copying the private image: {} -> {}'.format(
src_vm.private_img, self.vm.private_img))
self._copy_file(src_vm.private_img, self.vm.private_img)
if src_vm.updateable and hasattr(src_vm, 'root_img'):
self.vm.log.info('Copying the root image: {} -> {}'.format(
src_vm.root_img, self.root_img))
self._copy_file(src_vm.root_img, self.root_img)
# TODO: modules?
# XXX which modules? -woju
def rename(self, newpath, oldpath):
'''Move storage directory, most likely during domain's rename.
.. note::
The arguments are in different order than in :program:`cp` utility.
.. versionchange:: 3.0
This is now dummy method that just passes everything to
:py:func:`os.rename`.
:param str newpath: New path
:param str oldpath: Old path
'''
os.rename(oldpath, newpath)
def verify_files(self):
if not os.path.exists(self.vm.dir_path):
raise qubes.QubesException(
'VM directory does not exist: {}'.format(self.vmdir))
if hasattr(self.vm, 'root_img') and not os.path.exists(self.root_img):
raise qubes.QubesException(
'VM root image file does not exist: {}'.format(self.root_img))
if hasattr(self.vm, 'private_img') \
and not os.path.exists(self.private_img):
raise qubes.QubesException(
'VM private image file does not exist: {}'.format(
self.private_img))
if self.modules_img is not None \
and not os.path.exists(self.modules_img):
raise qubes.QubesException(
'VM kernel modules image does not exists: {}'.format(
self.modules_img))
def remove_from_disk(self):
shutil.rmtree(self.vm.dir_path)
def reset_volatile_storage(self, source_template=None):
if source_template is None:
source_template = self.vm.template
# Re-create only for template based VMs
if source_template is not None and self.volatile_img:
if os.path.exists(self.volatile_img):
os.remove(self.volatile_img)
# For StandaloneVM create it only if not already exists
# (eg after backup-restore)
if hasattr(self.vm, 'volatile_img') \
and not os.path.exists(self.vm.volatile_img):
self.vm.log.info(
'Creating volatile image: {0}'.format(self.volatile_img))
subprocess.check_call([system_path["prepare_volatile_img_cmd"],
self.volatile_img, str(self.root_img_size / 1024 / 1024)])
def prepare_for_vm_startup(self):
self.reset_volatile_storage()
if hasattr(self.vm, 'private_img') \
and not os.path.exists(self.private_img):
self.vm.log.info('Creating empty VM private image file: {0}'.format(
self.private_img))
self.storage.create_on_disk_private_img()
def get_storage(vm):
'''Factory yielding storage class instances for domains.
:raises ImportError: when storage class specified in config cannot be found
:raises KeyError: when storage class specified in config cannot be found
'''
pkg, cls = qubes.config.defaults['storage_class'].strip().rsplit('.', 1)
# this may raise ImportError or KeyError, that's okay
return importlib.import_module(pkg).__dict__[cls](vm)

179
qubes/storage/xen.py Normal file
View File

@ -0,0 +1,179 @@
#!/usr/bin/python2
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2013 Marek Marczykowski <marmarek@invisiblethingslab.com>
# Copyright (C) 2015 Wojtek Porczyk <woju@invisiblethingslab.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
#
from __future__ import absolute_import
import os
import os.path
import subprocess
import sys
import lxml.etree
import qubes
import qubes.config
import qubes.storage
import qubes.vm.templatevm
class XenVMStorage(qubes.storage.VMStorage):
'''Class for VM storage of Xen VMs.
'''
root_dev = 'xvda'
private_dev = 'xvdb'
volatile_dev = 'xvdc'
modules_dev = 'xvdd'
@staticmethod
def _format_disk_dev(path, vdev, script=None, rw=True, type='disk', domain=None):
if path is None:
return ''
element = lxml.etree.Element('disk')
element.set('type', 'block')
element.set('device', type)
element.append(lxml.etree.Element('driver', name='phy'))
element.append(lxml.etree.Element('source', dev=path))
element.append(lxml.etree.Element('target', dev=vdev))
if not rw:
element.append(lxml.etree.Element('readonly'))
if domain is not None:
# XXX vm.name?
element.append(lxml.etree.Element('domain', name=domain))
if script:
element.append(lxml.etree.Element('script', path=script))
# TODO return element
return lxml.etree.tostring(element)
def _get_rootdev(self):
if isinstance(self.vm, qubes.vm.templatevm.TemplateVM):
return self._format_disk_dev(
'{}:{}'.format(self.root_img, self.rootcow_img),
self.root_dev,
script='block-origin')
elif hasattr(self.vm, 'template'):
return self._format_disk_dev(
'{}:{}'.format(self.root_img, self.vm.template.rootcow_img),
self.root_dev,
script='block-snapshot')
else:
return self._format_disk_dev(self.root_img, self.root_dev)
def get_config_params(self):
args = {}
args['rootdev'] = self._get_rootdev()
args['privatedev'] = self._format_disk_dev(self.private_img,
self.private_dev)
args['volatiledev'] = self._format_disk_dev(self.volatile_img,
self.volatile_dev)
if self.modules_img is not None:
args['otherdevs'] = self._format_disk_dev(self.modules_img,
self.modules_dev, rw=self.modules_img_rw)
elif self.drive is not None:
(drive_type, drive_domain, drive_path) = self.drive.split(":")
if drive_domain.lower() == "dom0":
drive_domain = None
args['otherdevs'] = self._format_disk_dev(drive_path,
self.modules_dev,
rw=(drive_type == "disk"),
type=drive_type,
domain=drive_domain)
else:
args['otherdevs'] = ''
return args
def create_on_disk_private_img(self, source_template=None):
if source_template is None:
f_private = open(self.private_img, 'a+b')
f_private.truncate(self.private_img_size)
f_private.close()
else:
self.vm.log.info("Copying the template's private image: {}".format(
source_template.private_img))
self._copy_file(source_template.private_img, self.private_img)
def create_on_disk_root_img(self, source_template=None):
if source_template is None:
f_root = open (self.root_img, "a+b")
f_root.truncate (self.root_img_size)
f_root.close ()
elif self.vm.updateable:
# if not updateable, just use template's disk
self.vm.log.info("--> Copying the template's root image: {}".format(
source_template.root_img))
self._copy_file(source_template.root_img, self.root_img)
def resize_private_img(self, size):
f_private = open(self.private_img, "a+b")
f_private.truncate(size)
f_private.close()
# find loop device if any
p = subprocess.Popen(
['sudo', 'losetup', '--associated', self.private_img],
stdout=subprocess.PIPE)
result = p.communicate()
m = re.match(r'^(/dev/loop\d+):\s', result[0])
if m is not None:
loop_dev = m.group(1)
# resize loop device
subprocess.check_call(
['sudo', 'losetup', '--set-capacity', loop_dev])
def commit_template_changes(self):
assert isinstance(self.vm, qubes.vm.templatevm.TemplateVM)
# TODO: move rootcow_img to this class; the same for vm.is_outdated()
if os.path.exists(self.vm.rootcow_img):
os.rename(self.vm.rootcow_img, self.vm.rootcow_img + '.old')
old_umask = os.umask(002)
f_cow = open(self.vm.rootcow_img, 'w')
f_root = open(self.root_img, 'r')
f_root.seek(0, os.SEEK_END)
# make empty sparse file of the same size as root.img
f_cow.truncate(f_root.tell())
f_cow.close()
f_root.close()
os.umask(old_umask)

View File

@ -96,6 +96,9 @@ def _setter_kernel(self, prop, value):
return value
def _default_conf_file(self, name=None):
return (name or self.name) + '.conf'
class QubesVM(qubes.vm.BaseVM):
'''Base functionality of Qubes VM shared between all VMs.'''
@ -132,13 +135,15 @@ class QubesVM(qubes.vm.BaseVM):
uuid = qubes.property('uuid', type=uuid.UUID, default=None,
doc='UUID from libvirt.')
# TODO meaningful default
# TODO setter to ensure absolute/relative path?
dir_path = qubes.property('dir_path', type=str, default=None,
doc='FIXME')
conf_file = qubes.property('conf_file', type=str,
default=(lambda self: self.name + '.conf'),
default=_default_conf_file,
saver=(lambda self, prop, value: self.relative_path(value)),
doc='libvirt config file?')
doc='XXX libvirt config file?')
# XXX this should be part of qubes.xml
firewall_conf = qubes.property('firewall_conf', type=str,
@ -269,6 +274,7 @@ class QubesVM(qubes.vm.BaseVM):
if self._libvirt_domain is not None:
return self._libvirt_domain
# XXX _update_libvirt_domain?
try:
if self.uuid is not None:
self._libvirt_domain = vmm.libvirt_conn.lookupByUUID(self.uuid.bytes)
@ -323,7 +329,8 @@ class QubesVM(qubes.vm.BaseVM):
@property
def uses_custom_config(self):
'''True if this machine has config in non-standard place.'''
return self.conf_file != self.absolute_path(self.name + ".conf", None)
return not self.property_is_default(self, 'conf_file')
# return self.conf_file != self.storage.abspath(self.name + '.conf')
@property
def icon_path(self):
@ -509,35 +516,52 @@ class QubesVM(qubes.vm.BaseVM):
# self.netvm.post_vm_net_attach(self)
@qubes.events.handler('property-set:name')
def on_property_set_name(self, event, name, new_name, old_name=None):
@qubes.events.handler('property-pre-set:name')
def on_property_pre_set_name(self, event, name, newvalue, oldvalue=None):
# TODO not self.is_stopped() would be more appropriate
if self.is_running():
raise QubesException('Cannot change name of running domain')
if self.libvirt_domain:
@qubes.events.handler('property-pre-set:dir_path')
def on_property_pre_set_name(self, event, name, newvalue, oldvalue=None):
# TODO not self.is_stopped() would be more appropriate
if self.is_running():
raise QubesException('Cannot change dir_path of running domain')
@qubes.events.handler('property-set:dir_path')
def on_property_set_dir_path(self, event, name, newvalue, oldvalue=None):
self.storage.rename(newvalue, oldvalue)
@qubes.events.handler('property-set:name')
def on_property_set_name(self, event, name, new_name, old_name=None):
if self._libvirt_domain is not None:
self.libvirt_domain.undefine()
self._libvirt_domain = None
if self._qdb_connection:
self._libvirt_domain = None
if self._qdb_connection is not None:
self._qdb_connection.close()
self._qdb_connection = None
new_conf = os.path.join(self.dir_path, new_name + '.conf')
if os.path.exists(self.conf_file):
os.rename(self.conf_file, new_conf)
# move: dir_path, conf_file
self.dir_path = self.dir_path.replace(
'/{}/', '/{}/'.format(old_name, new_name))
old_dirpath = self.dir_path
if self.property_is_default(self, 'conf_file'):
new_conf = os.path.join(
self.dir_path, _default_conf_file(self, old_name))
old_conf = os.path.join(
self.dir_path, _default_conf_file(self, old_name))
self.storage.rename(old_conf, new_conf)
self.storage.rename(self.name, name)
new_dirpath = self.storage.vmdir
self.dir_path = new_dirpath
self.fire_event('property-set:conf_file', 'conf_file',
new_conf, old_conf)
if self.conf_file is not None:
self.conf_file = new_conf.replace(old_dirpath, new_dirpath)
if hasattr(self, 'kernels_dir') and self.kernels_dir is not None:
self.kernels_dir = self.kernels_dir.replace(old_dirpath, new_dirpath)
self._update_libvirt_domain()
self.post_rename(old_name)
@qubes.events.handler('property-pre-set:autostart')
@ -999,6 +1023,7 @@ class QubesVM(qubes.vm.BaseVM):
self.storage.resize_private_img(size)
# and then the filesystem
# FIXME move this to qubes.storage.xen.XenVMStorage
retcode = 0
if self.is_running():
retcode = self.run("while [ \"`blockdev --getsize64 /dev/xvdb`\" -lt {0} ]; do ".format(size) +
@ -1487,22 +1512,6 @@ class QubesVM(qubes.vm.BaseVM):
# helper methods
#
# TODO deprecate `default`
def absolute_path(self, path, default):
'''Return specified path as absolute path.
Relative paths are relative to :py:attr:`dir_path`. Absolute path are left unchanged.
:param str path: Path in question (possibly relatve).
:param default: What to return if ``arg`` is :py:obj:`None`.
:returns: Absolute path.
'''
if arg is not None and os.path.isabs(arg):
return arg
else:
return os.path.join(self.dir_path, (arg if arg is not None else default))
def relative_path(self, path):
'''Return path relative to py:attr:`dir_path`.
@ -1560,7 +1569,7 @@ class QubesVM(qubes.vm.BaseVM):
def _update_libvirt_domain(self):
'''Re-initialise :py:attr:`libvirt_domain`.'''
domain_config = self.create_config_file()
if self._libvirt_domain:
if self._libvirt_domain is not None:
self._libvirt_domain.undefine()
try:
self._libvirt_domain = vmm.libvirt_conn.defineXML(domain_config)

View File

@ -211,6 +211,10 @@ fi
%{python_sitearch}/qubes/vm/templatehvm.py*
%{python_sitearch}/qubes/vm/templatevm.py*
%dir %{python_sitearch}/qubes/storage
%{python_sitearch}/qubes/storage/__init__.py*
%{python_sitearch}/qubes/storage/xen.py*
%dir %{python_sitearch}/qubes/ext
%{python_sitearch}/qubes/ext/__init__.py*