core: split VM images handling to separate class
This will ease handling different types of VMM (which can require different image types, location etc).
This commit is contained in:
parent
d5cb05fdc6
commit
0a1f3d0a44
@ -347,6 +347,12 @@ class QubesVm(object):
|
||||
else:
|
||||
assert self.root_img is not None, "Missing root_img for standalone VM!"
|
||||
|
||||
self.storage = defaults["storage_class"](self)
|
||||
if hasattr(self, 'kernels_dir'):
|
||||
self.storage.modules_img = os.path.join(self.kernels_dir,
|
||||
"modules.img")
|
||||
self.storage.modules_img_rw = self.kernel is None
|
||||
|
||||
# fire hooks
|
||||
for hook in self.hooks_init:
|
||||
hook(self)
|
||||
@ -854,33 +860,17 @@ class QubesVm(object):
|
||||
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)
|
||||
return self.storage.get_private_img_sz()
|
||||
|
||||
def resize_private_img(self, size):
|
||||
assert size >= self.get_private_img_sz(), "Cannot shrink private.img"
|
||||
|
||||
f_private = open (self.private_img, "a+b")
|
||||
f_private.truncate (size)
|
||||
f_private.close ()
|
||||
# resize the image
|
||||
self.storage.resize_private_img(size)
|
||||
|
||||
# and then the filesystem
|
||||
retcode = 0
|
||||
if self.is_running():
|
||||
# find loop device
|
||||
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 None:
|
||||
raise QubesException("ERROR: Cannot find loop device!")
|
||||
|
||||
loop_dev = m.group(1)
|
||||
|
||||
# resize loop device
|
||||
subprocess.check_call(["sudo", "losetup", "--set-capacity", loop_dev])
|
||||
|
||||
retcode = self.run("while [ \"`blockdev --getsize64 /dev/xvdb`\" -lt {0} ]; do ".format(size) +
|
||||
"head /dev/xvdb > /dev/null; sleep 0.2; done; resize2fs /dev/xvdb", user="root", wait=True)
|
||||
if retcode != 0:
|
||||
@ -983,23 +973,6 @@ class QubesVm(object):
|
||||
for hook in self.hooks_create_xenstore_entries:
|
||||
hook(self, xid=xid)
|
||||
|
||||
def _format_disk_dev(self, path, script, vdev, rw=True, type="disk", domain=None):
|
||||
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 += " <domain name='%s'/>\n" % domain
|
||||
if script:
|
||||
params += " <script path='%s'/>\n" % script
|
||||
return template.format(path=path, vdev=vdev, type=type,
|
||||
params=params)
|
||||
|
||||
def _format_net_dev(self, ip, mac, backend):
|
||||
template = " <interface type='ethernet'>\n" \
|
||||
" <mac address='{mac}'/>\n" \
|
||||
@ -1023,17 +996,6 @@ class QubesVm(object):
|
||||
slot=dev_match.group(2),
|
||||
fun=dev_match.group(3))
|
||||
|
||||
def get_rootdev(self):
|
||||
if self.template:
|
||||
return self._format_disk_dev(
|
||||
"{dir}/root.img:{dir}/root-cow.img".format(
|
||||
dir=self.template.dir_path),
|
||||
"block-snapshot", "xvda", False)
|
||||
else:
|
||||
return self._format_disk_dev(
|
||||
"{dir}/root.img".format(dir=self.dir_path),
|
||||
None, "xvda", True)
|
||||
|
||||
def get_config_params(self):
|
||||
args = {}
|
||||
args['name'] = self.name
|
||||
@ -1070,17 +1032,7 @@ class QubesVm(object):
|
||||
args['netdev'] = ''
|
||||
args['disable_network1'] = '<!--';
|
||||
args['disable_network2'] = '-->';
|
||||
args['rootdev'] = self.get_rootdev()
|
||||
args['privatedev'] = \
|
||||
self._format_disk_dev("{dir}/private.img".format(dir=self.dir_path),
|
||||
None, "xvdb", True)
|
||||
args['volatiledev'] = \
|
||||
self._format_disk_dev("{dir}/volatile.img".format(dir=self.dir_path),
|
||||
None, "xvdc", True)
|
||||
if hasattr(self, 'kernel'):
|
||||
args['otherdevs'] = \
|
||||
self._format_disk_dev("{dir}/modules.img".format(dir=self.kernels_dir),
|
||||
None, "xvdd", self.kernel is None)
|
||||
args.update(self.storage.get_config_params())
|
||||
if hasattr(self, 'kernelopts'):
|
||||
args['kernelopts'] = self.kernelopts
|
||||
if self.debug:
|
||||
@ -1139,37 +1091,9 @@ class QubesVm(object):
|
||||
if dry_run:
|
||||
return
|
||||
|
||||
old_umask = os.umask(002)
|
||||
if verbose:
|
||||
print >> sys.stderr, "--> Creating directory: {0}".format(self.dir_path)
|
||||
os.mkdir (self.dir_path)
|
||||
|
||||
if verbose:
|
||||
print >> sys.stderr, "--> Creating the VM config file: {0}".format(self.conf_file)
|
||||
|
||||
template_priv = source_template.private_img
|
||||
if verbose:
|
||||
print >> sys.stderr, "--> Copying the template's private image: {0}".\
|
||||
format(template_priv)
|
||||
|
||||
# We prefer to use Linux's cp, because it nicely handles sparse files
|
||||
retcode = subprocess.call (["cp", template_priv, self.private_img])
|
||||
if retcode != 0:
|
||||
raise IOError ("Error while copying {0} to {1}".\
|
||||
format(template_priv, self.private_img))
|
||||
self.storage.create_on_disk(verbose, source_template)
|
||||
|
||||
if self.updateable:
|
||||
template_root = source_template.root_img
|
||||
if verbose:
|
||||
print >> sys.stderr, "--> Copying the template's root image: {0}".\
|
||||
format(template_root)
|
||||
|
||||
# We prefer to use Linux's cp, because it nicely handles sparse files
|
||||
retcode = subprocess.call (["cp", template_root, self.root_img])
|
||||
if retcode != 0:
|
||||
raise IOError ("Error while copying {0} to {1}".\
|
||||
format(template_root, self.root_img))
|
||||
|
||||
kernels_dir = source_template.kernels_dir
|
||||
if verbose:
|
||||
print >> sys.stderr, "--> Copying the kernel (set kernel \"none\" to use it): {0}".\
|
||||
@ -1180,15 +1104,10 @@ class QubesVm(object):
|
||||
shutil.copy(os.path.join(kernels_dir, f),
|
||||
os.path.join(self.dir_path, vm_files["kernels_subdir"], f))
|
||||
|
||||
# Create volatile.img
|
||||
self.reset_volatile_storage(source_template = source_template, verbose=verbose)
|
||||
|
||||
if verbose:
|
||||
print >> sys.stderr, "--> Creating icon symlink: {0} -> {1}".format(self.icon_path, self.label.icon_path)
|
||||
os.symlink (self.label.icon_path, self.icon_path)
|
||||
|
||||
os.umask(old_umask)
|
||||
|
||||
# fire hooks
|
||||
for hook in self.hooks_create_on_disk:
|
||||
hook(self, verbose, source_template=source_template)
|
||||
@ -1224,29 +1143,7 @@ class QubesVm(object):
|
||||
if src_vm.is_running():
|
||||
raise QubesException("Attempt to clone a running VM!")
|
||||
|
||||
if verbose:
|
||||
print >> sys.stderr, "--> Creating directory: {0}".format(self.dir_path)
|
||||
os.mkdir (self.dir_path)
|
||||
|
||||
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)
|
||||
# We prefer to use Linux's cp, because it nicely handles sparse files
|
||||
retcode = subprocess.call (["cp", src_vm.private_img, self.private_img])
|
||||
if retcode != 0:
|
||||
raise IOError ("Error while copying {0} to {1}".\
|
||||
format(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)
|
||||
# We prefer to use Linux's cp, because it nicely handles sparse files
|
||||
retcode = subprocess.call (["cp", src_vm.root_img, self.root_img])
|
||||
if retcode != 0:
|
||||
raise IOError ("Error while copying {0} to {1}".\
|
||||
format(src_vm.root_img, self.root_img))
|
||||
self.storage.clone_disk_files(src_vm, verbose)
|
||||
|
||||
if src_vm.icon_path is not None and self.icon_path is not None:
|
||||
if os.path.exists (src_vm.dir_path):
|
||||
@ -1268,20 +1165,7 @@ class QubesVm(object):
|
||||
if dry_run:
|
||||
return
|
||||
|
||||
if not os.path.exists (self.dir_path):
|
||||
raise QubesException (
|
||||
"VM directory doesn't exist: {0}".\
|
||||
format(self.dir_path))
|
||||
|
||||
if self.updateable and not os.path.exists (self.root_img):
|
||||
raise QubesException (
|
||||
"VM root image file doesn't exist: {0}".\
|
||||
format(self.root_img))
|
||||
|
||||
if not os.path.exists (self.private_img):
|
||||
raise QubesException (
|
||||
"VM private image file doesn't exist: {0}".\
|
||||
format(self.private_img))
|
||||
self.storage.verify_files()
|
||||
|
||||
if not os.path.exists (os.path.join(self.kernels_dir, 'vmlinuz')):
|
||||
raise QubesException (
|
||||
@ -1293,49 +1177,12 @@ class QubesVm(object):
|
||||
"VM initramfs does not exists: {0}".\
|
||||
format(os.path.join(self.kernels_dir, 'initramfs')))
|
||||
|
||||
if not os.path.exists (os.path.join(self.kernels_dir, 'modules.img')):
|
||||
raise QubesException (
|
||||
"VM kernel modules image does not exists: {0}".\
|
||||
format(os.path.join(self.kernels_dir, 'modules.img')))
|
||||
|
||||
# fire hooks
|
||||
for hook in self.hooks_verify_files:
|
||||
hook(self)
|
||||
|
||||
return True
|
||||
|
||||
def reset_volatile_storage(self, source_template = None, verbose = False):
|
||||
assert not self.is_running(), "Attempt to clean volatile image of running VM!"
|
||||
|
||||
if source_template is None:
|
||||
source_template = self.template
|
||||
|
||||
# Only makes sense on template based VM
|
||||
if source_template is None:
|
||||
# For StandaloneVM create it only if not already exists (eg after backup-restore)
|
||||
if not os.path.exists(self.volatile_img):
|
||||
if verbose:
|
||||
print >> sys.stderr, "--> Creating volatile image: {0}...".format (self.volatile_img)
|
||||
f_root = open (self.root_img, "r")
|
||||
f_root.seek(0, os.SEEK_END)
|
||||
root_size = f_root.tell()
|
||||
f_root.close()
|
||||
subprocess.check_call([system_path["prepare_volatile_img_cmd"], self.volatile_img, str(root_size / 1024 / 1024)])
|
||||
return
|
||||
|
||||
if verbose:
|
||||
print >> sys.stderr, "--> Cleaning volatile image: {0}...".format (self.volatile_img)
|
||||
if dry_run:
|
||||
return
|
||||
if os.path.exists (self.volatile_img):
|
||||
os.remove (self.volatile_img)
|
||||
|
||||
if hasattr(source_template, 'clean_volatile_img'):
|
||||
retcode = subprocess.call (["tar", "xf", source_template.clean_volatile_img, "-C", self.dir_path])
|
||||
if retcode != 0:
|
||||
raise IOError ("Error while unpacking {0} to {1}".\
|
||||
format(source_template.clean_volatile_img, self.volatile_img))
|
||||
|
||||
def remove_from_disk(self):
|
||||
if dry_run:
|
||||
return
|
||||
@ -1344,7 +1191,7 @@ class QubesVm(object):
|
||||
for hook in self.hooks_remove_from_disk:
|
||||
hook(self)
|
||||
|
||||
shutil.rmtree (self.dir_path)
|
||||
self.storage.remove_from_disk()
|
||||
|
||||
def write_firewall_conf(self, conf):
|
||||
defaults = self.get_firewall_conf()
|
||||
@ -1727,7 +1574,7 @@ class QubesVm(object):
|
||||
print >> sys.stderr, "--> Starting NetVM {0}...".format(self.netvm.name)
|
||||
self.netvm.start(verbose = verbose, start_guid = start_guid, notify_function = notify_function)
|
||||
|
||||
self.reset_volatile_storage(verbose=verbose)
|
||||
self.storage.prepare_for_vm_startup(verbose=verbose)
|
||||
if verbose:
|
||||
print >> sys.stderr, "--> Loading the VM (type = {0})...".format(self.type)
|
||||
|
||||
|
@ -51,6 +51,7 @@ class QubesTemplateVm(QubesVm):
|
||||
attrs_config['rootcow_img'] = {
|
||||
'func': lambda x: os.path.join(self.dir_path, vm_files["rootcow_img"]) }
|
||||
# Clean image for root-cow and swap (AppVM side)
|
||||
# TODO: not used anymore - clean up when all references removed
|
||||
attrs_config['clean_volatile_img'] = {
|
||||
'func': lambda x: os.path.join(self.dir_path, vm_files["clean_volatile_img"]) }
|
||||
|
||||
@ -76,35 +77,12 @@ class QubesTemplateVm(QubesVm):
|
||||
def get_firewall_defaults(self):
|
||||
return { "rules": list(), "allow": False, "allowDns": False, "allowIcmp": False, "allowYumProxy": True }
|
||||
|
||||
def get_rootdev(self):
|
||||
return self._format_disk_dev(
|
||||
"{dir}/root.img:{dir}/root-cow.img".format(
|
||||
dir=self.dir_path),
|
||||
"block-origin", "xvda", True)
|
||||
|
||||
def clone_disk_files(self, src_vm, verbose):
|
||||
if dry_run:
|
||||
return
|
||||
|
||||
super(QubesTemplateVm, self).clone_disk_files(src_vm=src_vm, verbose=verbose)
|
||||
|
||||
if verbose:
|
||||
print >> sys.stderr, "--> Copying the template's clean volatile image:\n{0} ==>\n{1}".\
|
||||
format(src_vm.clean_volatile_img, self.clean_volatile_img)
|
||||
# We prefer to use Linux's cp, because it nicely handles sparse files
|
||||
retcode = subprocess.call (["cp", src_vm.clean_volatile_img, self.clean_volatile_img])
|
||||
if retcode != 0:
|
||||
raise IOError ("Error while copying {0} to {1}".\
|
||||
format(src_vm.clean_volatile_img, self.clean_volatile_img))
|
||||
if verbose:
|
||||
print >> sys.stderr, "--> Copying the template's volatile image:\n{0} ==>\n{1}".\
|
||||
format(self.clean_volatile_img, self.volatile_img)
|
||||
# We prefer to use Linux's cp, because it nicely handles sparse files
|
||||
retcode = subprocess.call (["cp", self.clean_volatile_img, self.volatile_img])
|
||||
if retcode != 0:
|
||||
raise IOError ("Error while copying {0} to {1}".\
|
||||
format(self.clean_img, self.volatile_img))
|
||||
|
||||
# Create root-cow.img
|
||||
self.commit_changes(verbose=verbose)
|
||||
|
||||
@ -112,42 +90,10 @@ class QubesTemplateVm(QubesVm):
|
||||
super(QubesTemplateVm, self).post_rename(old_name)
|
||||
|
||||
old_dirpath = os.path.join(os.path.dirname(self.dir_path), old_name)
|
||||
# TODO: clean_volatile_img not used anymore
|
||||
self.clean_volatile_img = self.clean_volatile_img.replace(old_dirpath, self.dir_path)
|
||||
self.rootcow_img = self.rootcow_img.replace(old_dirpath, self.dir_path)
|
||||
|
||||
def verify_files(self):
|
||||
if dry_run:
|
||||
return
|
||||
|
||||
super(QubesTemplateVm, self).verify_files()
|
||||
|
||||
if not os.path.exists (self.volatile_img):
|
||||
raise QubesException (
|
||||
"VM volatile image file doesn't exist: {0}".\
|
||||
format(self.volatile_img))
|
||||
|
||||
if not os.path.exists (self.clean_volatile_img):
|
||||
raise QubesException (
|
||||
"Clean VM volatile image file doesn't exist: {0}".\
|
||||
format(self.clean_volatile_img))
|
||||
|
||||
return True
|
||||
|
||||
def reset_volatile_storage(self, verbose = False):
|
||||
assert not self.is_running(), "Attempt to clean volatile image of running Template VM!"
|
||||
|
||||
if verbose:
|
||||
print >> sys.stderr, "--> Cleaning volatile image: {0}...".format (self.volatile_img)
|
||||
if dry_run:
|
||||
return
|
||||
if os.path.exists (self.volatile_img):
|
||||
os.remove (self.volatile_img)
|
||||
|
||||
retcode = subprocess.call (["tar", "xf", self.clean_volatile_img, "-C", self.dir_path])
|
||||
if retcode != 0:
|
||||
raise IOError ("Error while unpacking {0} to {1}".\
|
||||
format(self.template.clean_volatile_img, self.volatile_img))
|
||||
|
||||
def commit_changes (self, verbose = False):
|
||||
|
||||
if not vmm.offline_mode:
|
||||
@ -158,16 +104,7 @@ class QubesTemplateVm(QubesVm):
|
||||
|
||||
if dry_run:
|
||||
return
|
||||
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)
|
||||
self.storage.commit_template_changes()
|
||||
|
||||
register_qubes_vm_class(QubesTemplateVm)
|
||||
|
@ -6,6 +6,7 @@ SETTINGS_SUFFIX = $(BACKEND_VMM)-$(OS)
|
||||
all:
|
||||
python -m compileall .
|
||||
python -O -m compileall .
|
||||
make -C storage all
|
||||
|
||||
install:
|
||||
ifndef PYTHON_SITEPATH
|
||||
@ -30,3 +31,4 @@ ifneq ($(BACKEND_VMM),)
|
||||
test -r settings-$(SETTINGS_SUFFIX).pyo && \
|
||||
cp settings-$(SETTINGS_SUFFIX).pyo $(DESTDIR)$(PYTHON_QUBESPATH)/settings.pyo
|
||||
endif
|
||||
make -C storage install
|
||||
|
@ -94,6 +94,11 @@ defaults = {
|
||||
|
||||
'dom0_update_check_interval': 6*3600,
|
||||
|
||||
'private_img_size': 2*1024*1024*1024,
|
||||
'root_img_size': 10*1024*1024*1024,
|
||||
|
||||
'storage_class': None,
|
||||
|
||||
# how long (in sec) to wait for VMs to shutdown,
|
||||
# before killing them (when used qvm-run with --wait option),
|
||||
'shutdown_counter_max': 60,
|
||||
|
21
core/storage/Makefile
Normal file
21
core/storage/Makefile
Normal file
@ -0,0 +1,21 @@
|
||||
OS ?= Linux
|
||||
|
||||
PYTHON_QUBESPATH = $(PYTHON_SITEPATH)/qubes
|
||||
|
||||
all:
|
||||
python -m compileall .
|
||||
python -O -m compileall .
|
||||
|
||||
install:
|
||||
ifndef PYTHON_SITEPATH
|
||||
$(error PYTHON_SITEPATH not defined)
|
||||
endif
|
||||
mkdir -p $(DESTDIR)$(PYTHON_QUBESPATH)/storage
|
||||
cp __init__.py $(DESTDIR)$(PYTHON_QUBESPATH)/storage
|
||||
cp __init__.py[co] $(DESTDIR)$(PYTHON_QUBESPATH)/storage
|
||||
ifneq ($(BACKEND_VMM),)
|
||||
if [ -r $(BACKEND_VMM).py ]; then \
|
||||
cp $(BACKEND_VMM).py $(DESTDIR)$(PYTHON_QUBESPATH)/storage && \
|
||||
cp $(BACKEND_VMM).py[co] $(DESTDIR)$(PYTHON_QUBESPATH)/storage; \
|
||||
fi
|
||||
endif
|
182
core/storage/__init__.py
Normal file
182
core/storage/__init__.py
Normal file
@ -0,0 +1,182 @@
|
||||
#!/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
|
||||
|
||||
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 verify_files(self):
|
||||
if not os.path.exists (self.vmdir):
|
||||
raise QubesException (
|
||||
"VM directory doesn't exist: {0}".\
|
||||
format(self.vmdir))
|
||||
|
||||
if self.vm.updateable and not os.path.exists (self.root_img):
|
||||
raise QubesException (
|
||||
"VM root image file doesn't exist: {0}".\
|
||||
format(self.root_img))
|
||||
|
||||
if 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:
|
||||
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 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)
|
||||
pass
|
154
core/storage/xen.py
Normal file
154
core/storage/xen.py
Normal file
@ -0,0 +1,154 @@
|
||||
#!/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
|
||||
|
||||
from qubes.storage import QubesVmStorage
|
||||
from qubes.qubes import QubesException
|
||||
|
||||
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"
|
||||
|
||||
def _format_disk_dev(self, path, script, vdev, rw=True, type="disk", domain=None):
|
||||
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 += " <domain 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():
|
||||
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.vmdir),
|
||||
"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)
|
||||
|
||||
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 ()
|
||||
|
||||
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.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)
|
||||
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)
|
||||
|
@ -197,6 +197,9 @@ fi
|
||||
%{python_sitearch}/qubes/backup.py
|
||||
%{python_sitearch}/qubes/backup.pyc
|
||||
%{python_sitearch}/qubes/backup.pyo
|
||||
%{python_sitearch}/qubes/storage/*.py
|
||||
%{python_sitearch}/qubes/storage/*.pyc
|
||||
%{python_sitearch}/qubes/storage/*.pyo
|
||||
%{python_sitearch}/qubes/qmemman*.py*
|
||||
%{python_sitearch}/qubes/modules/0*.py*
|
||||
%{python_sitearch}/qubes/modules/__init__.py*
|
||||
|
Loading…
Reference in New Issue
Block a user