Browse Source

core: split VM images handling to separate class

This will ease handling different types of VMM (which can require
different image types, location etc).
Marek Marczykowski-Górecki 11 năm trước cách đây
mục cha
commit
0a1f3d0a44

+ 16 - 169
core-modules/000QubesVm.py

@@ -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)
 

+ 4 - 67
core-modules/003QubesTemplateVm.py

@@ -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)

+ 2 - 0
core/Makefile

@@ -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

+ 5 - 0
core/qubes.py

@@ -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 - 0
core/storage/Makefile

@@ -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 - 0
core/storage/__init__.py

@@ -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 - 0
core/storage/xen.py

@@ -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)
+

+ 3 - 0
rpm_spec/core-dom0.spec

@@ -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*