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 -p $(DESTDIR)$(PYTHON_QUBESPATH)
|
||||||
mkdir \
|
mkdir \
|
||||||
$(DESTDIR)$(PYTHON_QUBESPATH)/vm \
|
$(DESTDIR)$(PYTHON_QUBESPATH)/vm \
|
||||||
|
$(DESTDIR)$(PYTHON_QUBESPATH)/storage \
|
||||||
$(DESTDIR)$(PYTHON_QUBESPATH)/ext \
|
$(DESTDIR)$(PYTHON_QUBESPATH)/ext \
|
||||||
$(DESTDIR)$(PYTHON_QUBESPATH)/tests \
|
$(DESTDIR)$(PYTHON_QUBESPATH)/tests \
|
||||||
$(DESTDIR)$(PYTHON_QUBESPATH)/tests/vm
|
$(DESTDIR)$(PYTHON_QUBESPATH)/tests/vm
|
||||||
@ -43,6 +44,11 @@ endif
|
|||||||
vm/templatevm.py* \
|
vm/templatevm.py* \
|
||||||
$(DESTDIR)$(PYTHON_QUBESPATH)/vm
|
$(DESTDIR)$(PYTHON_QUBESPATH)/vm
|
||||||
|
|
||||||
|
cp \
|
||||||
|
storage/__init__.py* \
|
||||||
|
storage/xen.py* \
|
||||||
|
$(DESTDIR)$(PYTHON_QUBESPATH)/storage
|
||||||
|
|
||||||
cp ext/__init__.py* $(DESTDIR)$(PYTHON_QUBESPATH)/ext
|
cp ext/__init__.py* $(DESTDIR)$(PYTHON_QUBESPATH)/ext
|
||||||
|
|
||||||
cp \
|
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
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def _default_conf_file(self, name=None):
|
||||||
|
return (name or self.name) + '.conf'
|
||||||
|
|
||||||
|
|
||||||
class QubesVM(qubes.vm.BaseVM):
|
class QubesVM(qubes.vm.BaseVM):
|
||||||
'''Base functionality of Qubes VM shared between all VMs.'''
|
'''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,
|
uuid = qubes.property('uuid', type=uuid.UUID, default=None,
|
||||||
doc='UUID from libvirt.')
|
doc='UUID from libvirt.')
|
||||||
|
|
||||||
|
# TODO meaningful default
|
||||||
|
# TODO setter to ensure absolute/relative path?
|
||||||
dir_path = qubes.property('dir_path', type=str, default=None,
|
dir_path = qubes.property('dir_path', type=str, default=None,
|
||||||
doc='FIXME')
|
doc='FIXME')
|
||||||
|
|
||||||
conf_file = qubes.property('conf_file', type=str,
|
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)),
|
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
|
# XXX this should be part of qubes.xml
|
||||||
firewall_conf = qubes.property('firewall_conf', type=str,
|
firewall_conf = qubes.property('firewall_conf', type=str,
|
||||||
@ -269,6 +274,7 @@ class QubesVM(qubes.vm.BaseVM):
|
|||||||
if self._libvirt_domain is not None:
|
if self._libvirt_domain is not None:
|
||||||
return self._libvirt_domain
|
return self._libvirt_domain
|
||||||
|
|
||||||
|
# XXX _update_libvirt_domain?
|
||||||
try:
|
try:
|
||||||
if self.uuid is not None:
|
if self.uuid is not None:
|
||||||
self._libvirt_domain = vmm.libvirt_conn.lookupByUUID(self.uuid.bytes)
|
self._libvirt_domain = vmm.libvirt_conn.lookupByUUID(self.uuid.bytes)
|
||||||
@ -323,7 +329,8 @@ class QubesVM(qubes.vm.BaseVM):
|
|||||||
@property
|
@property
|
||||||
def uses_custom_config(self):
|
def uses_custom_config(self):
|
||||||
'''True if this machine has config in non-standard place.'''
|
'''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
|
@property
|
||||||
def icon_path(self):
|
def icon_path(self):
|
||||||
@ -509,35 +516,52 @@ class QubesVM(qubes.vm.BaseVM):
|
|||||||
# self.netvm.post_vm_net_attach(self)
|
# self.netvm.post_vm_net_attach(self)
|
||||||
|
|
||||||
|
|
||||||
@qubes.events.handler('property-set:name')
|
@qubes.events.handler('property-pre-set:name')
|
||||||
def on_property_set_name(self, event, name, new_name, old_name=None):
|
def on_property_pre_set_name(self, event, name, newvalue, oldvalue=None):
|
||||||
# TODO not self.is_stopped() would be more appropriate
|
# TODO not self.is_stopped() would be more appropriate
|
||||||
if self.is_running():
|
if self.is_running():
|
||||||
raise QubesException('Cannot change name of running domain')
|
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.undefine()
|
||||||
self._libvirt_domain = None
|
self._libvirt_domain = None
|
||||||
if self._qdb_connection:
|
if self._qdb_connection is not None:
|
||||||
self._qdb_connection.close()
|
self._qdb_connection.close()
|
||||||
self._qdb_connection = None
|
self._qdb_connection = None
|
||||||
|
|
||||||
new_conf = os.path.join(self.dir_path, new_name + '.conf')
|
# move: dir_path, conf_file
|
||||||
if os.path.exists(self.conf_file):
|
self.dir_path = self.dir_path.replace(
|
||||||
os.rename(self.conf_file, new_conf)
|
'/{}/', '/{}/'.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)
|
self.fire_event('property-set:conf_file', 'conf_file',
|
||||||
new_dirpath = self.storage.vmdir
|
new_conf, old_conf)
|
||||||
self.dir_path = new_dirpath
|
|
||||||
|
|
||||||
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:
|
if hasattr(self, 'kernels_dir') and self.kernels_dir is not None:
|
||||||
self.kernels_dir = self.kernels_dir.replace(old_dirpath, new_dirpath)
|
self.kernels_dir = self.kernels_dir.replace(old_dirpath, new_dirpath)
|
||||||
|
|
||||||
self._update_libvirt_domain()
|
self._update_libvirt_domain()
|
||||||
self.post_rename(old_name)
|
|
||||||
|
|
||||||
|
|
||||||
@qubes.events.handler('property-pre-set:autostart')
|
@qubes.events.handler('property-pre-set:autostart')
|
||||||
@ -999,6 +1023,7 @@ class QubesVM(qubes.vm.BaseVM):
|
|||||||
self.storage.resize_private_img(size)
|
self.storage.resize_private_img(size)
|
||||||
|
|
||||||
# and then the filesystem
|
# and then the filesystem
|
||||||
|
# FIXME move this to qubes.storage.xen.XenVMStorage
|
||||||
retcode = 0
|
retcode = 0
|
||||||
if self.is_running():
|
if self.is_running():
|
||||||
retcode = self.run("while [ \"`blockdev --getsize64 /dev/xvdb`\" -lt {0} ]; do ".format(size) +
|
retcode = self.run("while [ \"`blockdev --getsize64 /dev/xvdb`\" -lt {0} ]; do ".format(size) +
|
||||||
@ -1487,22 +1512,6 @@ class QubesVM(qubes.vm.BaseVM):
|
|||||||
# helper methods
|
# 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):
|
def relative_path(self, path):
|
||||||
'''Return path relative to py:attr:`dir_path`.
|
'''Return path relative to py:attr:`dir_path`.
|
||||||
|
|
||||||
@ -1560,7 +1569,7 @@ class QubesVM(qubes.vm.BaseVM):
|
|||||||
def _update_libvirt_domain(self):
|
def _update_libvirt_domain(self):
|
||||||
'''Re-initialise :py:attr:`libvirt_domain`.'''
|
'''Re-initialise :py:attr:`libvirt_domain`.'''
|
||||||
domain_config = self.create_config_file()
|
domain_config = self.create_config_file()
|
||||||
if self._libvirt_domain:
|
if self._libvirt_domain is not None:
|
||||||
self._libvirt_domain.undefine()
|
self._libvirt_domain.undefine()
|
||||||
try:
|
try:
|
||||||
self._libvirt_domain = vmm.libvirt_conn.defineXML(domain_config)
|
self._libvirt_domain = vmm.libvirt_conn.defineXML(domain_config)
|
||||||
|
@ -211,6 +211,10 @@ fi
|
|||||||
%{python_sitearch}/qubes/vm/templatehvm.py*
|
%{python_sitearch}/qubes/vm/templatehvm.py*
|
||||||
%{python_sitearch}/qubes/vm/templatevm.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
|
%dir %{python_sitearch}/qubes/ext
|
||||||
%{python_sitearch}/qubes/ext/__init__.py*
|
%{python_sitearch}/qubes/ext/__init__.py*
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user