瀏覽代碼

core3 move: storage/*

Wojtek Porczyk 9 年之前
父節點
當前提交
8afba4c5e9
共有 7 個文件被更改,包括 478 次插入421 次删除
  1. 0 201
      core/storage/__init__.py
  2. 0 185
      core/storage/xen.py
  3. 6 0
      qubes/Makefile
  4. 245 0
      qubes/storage/__init__.py
  5. 179 0
      qubes/storage/xen.py
  6. 44 35
      qubes/vm/qubesvm.py
  7. 4 0
      rpm_spec/core-dom0.spec

+ 0 - 201
core/storage/__init__.py

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

+ 0 - 185
core/storage/xen.py

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

+ 6 - 0
qubes/Makefile

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

+ 245 - 0
qubes/storage/__init__.py

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

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

+ 44 - 35
qubes/vm/qubesvm.py

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

+ 4 - 0
rpm_spec/core-dom0.spec

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