core3 move: storage/*
This commit is contained in:
parent
0dbcdb8c0d
commit
8afba4c5e9
@ -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)
|
@ -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)
|
||||
|
@ -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
245
qubes/storage/__init__.py
Normal 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
179
qubes/storage/xen.py
Normal 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)
|
@ -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)
|
||||
|
@ -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*
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user