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. def format_disk_dev(self, path, vdev, script=None, rw=True, devtype='disk',
  72. domain=None):
  73. if path is None:
  74. return ''
  75. element = lxml.etree.Element('disk')
  76. element.set('type', 'block')
  77. element.set('device', type)
  78. element.append(lxml.etree.Element('driver', name='phy'))
  79. element.append(lxml.etree.Element('source', dev=path))
  80. element.append(lxml.etree.Element('target', dev=vdev))
  81. if not rw:
  82. element.append(lxml.etree.Element('readonly'))
  83. if domain is not None:
  84. # XXX vm.name?
  85. element.append(lxml.etree.Element('domain', name=domain))
  86. if script:
  87. element.append(lxml.etree.Element('script', path=script))
  88. # TODO return element
  89. return lxml.etree.tostring(element)
  90. def root_dev_config(self):
  91. if isinstance(self.vm, qubes.vm.templatevm.TemplateVM):
  92. return self.format_disk_dev(
  93. '{root}:{rootcow}'.format(
  94. root=self.root_img,
  95. rootcow=self.rootcow_img),
  96. self.root_dev,
  97. script='block-origin')
  98. elif self.vm.hvm and hasattr(self.vm, 'template'):
  99. # HVM template-based VM - only one device-mapper layer, in dom0
  100. # (root+volatile)
  101. # HVM detection based on 'kernel' property is massive hack,
  102. # but taken from assumption that VM needs Qubes-specific kernel
  103. # (actually initramfs) to assemble the second layer of device-mapper
  104. return self.format_disk_dev(
  105. '{root}:{volatile}'.format(
  106. root=self.vm.template.storage.root_img,
  107. volatile=self.volatile_img),
  108. self.root_dev,
  109. script='block-snapshot')
  110. elif hasattr(self.vm, 'template'):
  111. # any other template-based VM - two device-mapper layers: one
  112. # in dom0 (here) from root+root-cow, and another one from
  113. # this+volatile.img
  114. return self.format_disk_dev(
  115. '{root}:{template_rootcow}'.format(
  116. root=self.root_img,
  117. template_rootcow=self.vm.template.storage.rootcow_img),
  118. self.root_dev,
  119. script='block-snapshot',
  120. rw=False)
  121. else:
  122. # standalone qube
  123. return self.format_disk_dev(self.root_img, self.root_dev)
  124. def private_dev_config(self):
  125. self.format_disk_dev(self.private_img, self.private_dev)
  126. def volatile_dev_config(self):
  127. self.format_disk_dev(self.volatile_img, self.volatile_dev)
  128. def create_on_disk_private_img(self, source_template=None):
  129. if source_template is None:
  130. f_private = open(self.private_img, 'a+b')
  131. f_private.truncate(self.private_img_size)
  132. f_private.close()
  133. else:
  134. self.vm.log.info("Copying the template's private image: {}".format(
  135. source_template.private_img))
  136. self._copy_file(source_template.private_img, self.private_img)
  137. def create_on_disk_root_img(self, source_template=None):
  138. if source_template is None:
  139. fd = open(self.root_img, 'a+b')
  140. fd.truncate(self.root_img_size)
  141. fd.close()
  142. elif self.vm.updateable:
  143. # if not updateable, just use template's disk
  144. self.vm.log.info("--> Copying the template's root image: {}".format(
  145. source_template.root_img))
  146. self._copy_file(source_template.root_img, self.root_img)
  147. def resize_private_img(self, size):
  148. fd = open(self.private_img, 'a+b')
  149. fd.truncate(size)
  150. fd.close()
  151. # find loop device if any
  152. p = subprocess.Popen(
  153. ['sudo', 'losetup', '--associated', self.private_img],
  154. stdout=subprocess.PIPE)
  155. result = p.communicate()
  156. m = re.match(r'^(/dev/loop\d+):\s', result[0])
  157. if m is not None:
  158. loop_dev = m.group(1)
  159. # resize loop device
  160. subprocess.check_call(
  161. ['sudo', 'losetup', '--set-capacity', loop_dev])
  162. def commit_template_changes(self):
  163. assert isinstance(self.vm, qubes.vm.templatevm.TemplateVM)
  164. # TODO: move rootcow_img to this class; the same for vm.is_outdated()
  165. if os.path.exists(self.vm.rootcow_img):
  166. os.rename(self.vm.rootcow_img, self.vm.rootcow_img + '.old')
  167. old_umask = os.umask(002)
  168. f_cow = open(self.vm.rootcow_img, 'w')
  169. f_root = open(self.root_img, 'r')
  170. f_root.seek(0, os.SEEK_END)
  171. # make empty sparse file of the same size as root.img
  172. f_cow.truncate(f_root.tell())
  173. f_cow.close()
  174. f_root.close()
  175. os.umask(old_umask)
  176. def reset_volatile_storage(self, source_template=None):
  177. if source_template is None:
  178. source_template = self.vm.template
  179. if source_template is not None:
  180. # template-based VM with only one device-mapper layer -
  181. # volatile.img used as upper layer on root.img, no root-cow.img
  182. # intermediate layer
  183. # XXX marmarek says this is either always true or always false;
  184. # rootcow_img got smashed in 35cb82 (#1573)
  185. # this may have remain after HVM check
  186. # this probably should have happen anyway
  187. if not source_template.storage.rootcow_img:
  188. if os.path.exists(self.volatile_img):
  189. if self.vm.debug:
  190. if os.path.getmtime(source_template.storage.root_img) \
  191. > os.path.getmtime(self.volatile_img):
  192. self.vm.log.warning(
  193. 'Template have changed, resetting root.img')
  194. else:
  195. self.vm.log.warning(
  196. 'Debug mode: not resetting root.img; if you'
  197. ' want to force root.img reset, either'
  198. ' update template VM, or remove volatile.img'
  199. ' file.')
  200. return
  201. os.remove(self.volatile_img)
  202. # FIXME stat on f_root; with open() ...
  203. f_volatile = open(self.volatile_img, "w")
  204. f_root = open(source_template.storage.root_img, "r")
  205. # make empty sparse file of the same size as root.img
  206. f_root.seek(0, os.SEEK_END)
  207. f_volatile.truncate(f_root.tell())
  208. f_volatile.close()
  209. f_root.close()
  210. return # XXX why is that? super() does not run
  211. super(XenStorage, self).reset_volatile_storage(
  212. source_template=source_template)
  213. def prepare_for_vm_startup(self):
  214. super(XenStorage, self).prepare_for_vm_startup()
  215. if self.drive is not None:
  216. # pylint: disable=unused-variable
  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)