xen.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. #!/usr/bin/python2 -O
  2. # vim: fileencoding=utf-8
  3. #
  4. # The Qubes OS Project, https://www.qubes-os.org/
  5. #
  6. # Copyright (C) 2015 Joanna Rutkowska <joanna@invisiblethingslab.com>
  7. # Copyright (C) 2013-2015 Marek Marczykowski-Górecki
  8. # <marmarek@invisiblethingslab.com>
  9. # Copyright (C) 2015 Wojtek Porczyk <woju@invisiblethingslab.com>
  10. #
  11. # This program is free software; you can redistribute it and/or modify
  12. # it under the terms of the GNU General Public License as published by
  13. # the Free Software Foundation; either version 2 of the License, or
  14. # (at your option) any later version.
  15. #
  16. # This program is distributed in the hope that it will be useful,
  17. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  19. # GNU General Public License for more details.
  20. #
  21. # You should have received a copy of the GNU General Public License along
  22. # with this program; if not, write to the Free Software Foundation, Inc.,
  23. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  24. #
  25. from __future__ import absolute_import
  26. import os
  27. import os.path
  28. import re
  29. import subprocess
  30. import lxml.etree
  31. import qubes
  32. import qubes.config
  33. import qubes.storage
  34. import qubes.vm.templatevm
  35. class XenStorage(qubes.storage.Storage):
  36. '''Class for VM storage of Xen VMs.
  37. '''
  38. root_dev = 'xvda'
  39. private_dev = 'xvdb'
  40. volatile_dev = 'xvdc'
  41. modules_dev = 'xvdd'
  42. def __init__(self, vm, vmdir, **kwargs):
  43. """ Instantiate the storage.
  44. Args:
  45. vm: a QubesVM
  46. vmdir: the root directory of the pool
  47. """
  48. assert vm is not None
  49. assert vmdir is not None
  50. super(XenStorage, self).__init__(vm, **kwargs)
  51. self.vmdir = vmdir
  52. @property
  53. def private_img(self):
  54. '''Path to the private image'''
  55. return self.abspath(qubes.config.vm_files['private_img'])
  56. @property
  57. def root_img(self):
  58. '''Path to the root image'''
  59. return self.vm.template.root_img if hasattr(self.vm, 'template') \
  60. else self.abspath(qubes.config.vm_files['root_img'])
  61. @property
  62. def rootcow_img(self):
  63. '''Path to the root COW image'''
  64. if isinstance(self.vm, qubes.vm.templatevm.TemplateVM):
  65. return self.abspath(qubes.config.vm_files['rootcow_img'])
  66. return None
  67. @property
  68. def volatile_img(self):
  69. '''Path to the volatile image'''
  70. return self.abspath(qubes.config.vm_files['volatile_img'])
  71. # pylint: disable=redefined-builtin
  72. @staticmethod
  73. def format_disk_dev(path, vdev, script=None, rw=True, type='disk',
  74. domain=None):
  75. if path is None:
  76. return ''
  77. element = lxml.etree.Element('disk')
  78. element.set('type', 'block')
  79. element.set('device', type)
  80. element.append(lxml.etree.Element('driver', name='phy'))
  81. element.append(lxml.etree.Element('source', dev=path))
  82. element.append(lxml.etree.Element('target', dev=vdev))
  83. if not rw:
  84. element.append(lxml.etree.Element('readonly'))
  85. if domain is not None:
  86. # XXX vm.name?
  87. element.append(lxml.etree.Element('domain', name=domain))
  88. if script:
  89. element.append(lxml.etree.Element('script', path=script))
  90. # TODO return element
  91. return lxml.etree.tostring(element)
  92. def root_dev_config(self):
  93. if isinstance(self.vm, qubes.vm.templatevm.TemplateVM):
  94. return self.format_disk_dev(
  95. '{root}:{rootcow}'.format(
  96. root=self.root_img,
  97. rootcow=self.rootcow_img),
  98. self.root_dev,
  99. script='block-origin')
  100. elif self.vm.hvm and hasattr(self.vm, 'template'):
  101. # HVM template-based VM - only one device-mapper layer, in dom0
  102. # (root+volatile)
  103. # HVM detection based on 'kernel' property is massive hack,
  104. # but taken from assumption that VM needs Qubes-specific kernel
  105. # (actually initramfs) to assemble the second layer of device-mapper
  106. return self.format_disk_dev(
  107. '{root}:{volatile}'.format(
  108. root=self.vm.template.storage.root_img,
  109. volatile=self.volatile_img),
  110. self.root_dev,
  111. script='block-snapshot')
  112. elif hasattr(self.vm, 'template'):
  113. # any other template-based VM - two device-mapper layers: one
  114. # in dom0 (here) from root+root-cow, and another one from
  115. # this+volatile.img
  116. return self.format_disk_dev(
  117. '{root}:{template_rootcow}'.format(
  118. root=self.root_img,
  119. template_rootcow=self.vm.template.storage.rootcow_img),
  120. self.root_dev,
  121. script='block-snapshot',
  122. rw=False)
  123. else:
  124. # standalone qube
  125. return self.format_disk_dev(self.root_img, self.root_dev)
  126. def private_dev_config(self):
  127. self.format_disk_dev(self.private_img, self.private_dev)
  128. def volatile_dev_config(self):
  129. self.format_disk_dev(self.volatile_img, self.volatile_dev)
  130. def create_on_disk_private_img(self, source_template=None):
  131. if source_template is None:
  132. f_private = open(self.private_img, 'a+b')
  133. f_private.truncate(self.private_img_size)
  134. f_private.close()
  135. else:
  136. self.vm.log.info("Copying the template's private image: {}".format(
  137. source_template.private_img))
  138. self._copy_file(source_template.private_img, self.private_img)
  139. def create_on_disk_root_img(self, source_template=None):
  140. if source_template is None:
  141. fd = open(self.root_img, 'a+b')
  142. fd.truncate(self.root_img_size)
  143. fd.close()
  144. elif self.vm.updateable:
  145. # if not updateable, just use template's disk
  146. self.vm.log.info("--> Copying the template's root image: {}".format(
  147. source_template.root_img))
  148. self._copy_file(source_template.root_img, self.root_img)
  149. def resize_private_img(self, size):
  150. fd = open(self.private_img, 'a+b')
  151. fd.truncate(size)
  152. fd.close()
  153. # find loop device if any
  154. p = subprocess.Popen(
  155. ['sudo', 'losetup', '--associated', self.private_img],
  156. stdout=subprocess.PIPE)
  157. result = p.communicate()
  158. m = re.match(r'^(/dev/loop\d+):\s', result[0])
  159. if m is not None:
  160. loop_dev = m.group(1)
  161. # resize loop device
  162. subprocess.check_call(
  163. ['sudo', 'losetup', '--set-capacity', loop_dev])
  164. def commit_template_changes(self):
  165. assert isinstance(self.vm, qubes.vm.templatevm.TemplateVM)
  166. # TODO: move rootcow_img to this class; the same for vm.is_outdated()
  167. if os.path.exists(self.vm.rootcow_img):
  168. os.rename(self.vm.rootcow_img, self.vm.rootcow_img + '.old')
  169. old_umask = os.umask(002)
  170. f_cow = open(self.vm.rootcow_img, 'w')
  171. f_root = open(self.root_img, 'r')
  172. f_root.seek(0, os.SEEK_END)
  173. # make empty sparse file of the same size as root.img
  174. f_cow.truncate(f_root.tell())
  175. f_cow.close()
  176. f_root.close()
  177. os.umask(old_umask)
  178. def reset_volatile_storage(self, source_template=None):
  179. if source_template is None:
  180. source_template = self.vm.template
  181. if source_template is not None:
  182. # template-based VM with only one device-mapper layer -
  183. # volatile.img used as upper layer on root.img, no root-cow.img
  184. # intermediate layer
  185. # XXX marmarek says this is either always true or always false;
  186. # rootcow_img got smashed in 35cb82 (#1573)
  187. # this may have remain after HVM check
  188. # this probably should have happen anyway
  189. if not source_template.storage.rootcow_img:
  190. if os.path.exists(self.volatile_img):
  191. if self.vm.debug:
  192. if os.path.getmtime(source_template.storage.root_img) \
  193. > os.path.getmtime(self.volatile_img):
  194. self.vm.log.warning(
  195. 'Template have changed, resetting root.img')
  196. else:
  197. self.vm.log.warning(
  198. 'Debug mode: not resetting root.img; if you'
  199. ' want to force root.img reset, either'
  200. ' update template VM, or remove volatile.img'
  201. ' file.')
  202. return
  203. os.remove(self.volatile_img)
  204. # FIXME stat on f_root; with open() ...
  205. f_volatile = open(self.volatile_img, "w")
  206. f_root = open(source_template.storage.root_img, "r")
  207. f_root.seek(0, os.SEEK_END)
  208. f_volatile.truncate(f_root.tell()) # make empty sparse file of the same size as root.img
  209. f_volatile.close()
  210. f_root.close()
  211. return # XXX why is that? super() does not run
  212. super(XenStorage, self).reset_volatile_storage(
  213. source_template=source_template)
  214. def prepare_for_vm_startup(self):
  215. super(XenStorage, self).prepare_for_vm_startup()
  216. if self.drive is not None:
  217. (drive_type, drive_domain, drive_path) = self.drive.split(":")
  218. if drive_domain.lower() != "dom0":
  219. # XXX "VM '{}' holding '{}' does not exists".format(
  220. drive_vm = self.vm.app.domains[drive_domain]
  221. if not drive_vm.is_running():
  222. raise qubes.exc.QubesVMNotRunningError(drive_vm,
  223. 'VM {!r} holding {!r} isn\'t running'.format(
  224. drive_domain, drive_path))
  225. if self.rootcow_img and not os.path.exists(self.rootcow_img):
  226. self.commit_template_changes()
  227. class XenPool(qubes.storage.Pool):
  228. def get_storage(self):
  229. """ Returns an instantiated ``XenStorage``. """
  230. return XenStorage(self.vm, vmdir=self.vmdir)