__init__.py 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  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) 2013-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 importlib
  27. import os
  28. import os.path
  29. import re
  30. import shutil
  31. import subprocess
  32. import sys
  33. import qubes
  34. import qubes.utils
  35. class VMStorage(object):
  36. '''Class for handling VM virtual disks.
  37. This is base class for all other implementations, mostly with Xen on Linux
  38. in mind.
  39. ''' # pylint: disable=abstract-class-little-used
  40. def __init__(self, vm, private_img_size=None, root_img_size=None):
  41. #: Domain for which we manage storage
  42. self.vm = vm
  43. #: Size of the private image
  44. self.private_img_size = private_img_size \
  45. if private_img_size is not None \
  46. else qubes.config.defaults['private_img_size']
  47. #: Size of the root image
  48. self.root_img_size = root_img_size \
  49. if root_img_size is not None \
  50. else qubes.config.defaults['root_img_size']
  51. #: Additional drive (currently used only by HVM)
  52. self.drive = None
  53. @property
  54. def private_img(self):
  55. '''Path to the private image'''
  56. return self.abspath(qubes.config.vm_files['private_img'])
  57. @property
  58. def root_img(self):
  59. '''Path to the root image'''
  60. return self.vm.template.root_img if hasattr(self.vm, 'template') \
  61. else self.abspath(qubes.config.vm_files['root_img'])
  62. @property
  63. def volatile_img(self):
  64. '''Path to the volatile image'''
  65. return self.abspath(qubes.config.vm_files['volatile_img'])
  66. @property
  67. def kernels_dir(self):
  68. '''Directory where kernel resides.
  69. If :py:attr:`self.vm.kernel` is :py:obj:`None`, the this points inside
  70. :py:attr:`self.vm.dir_path`
  71. '''
  72. return os.path.join(qubes.config.system_path['qubes_base_dir'],
  73. qubes.config.system_path['qubes_kernels_base_dir'], self.vm.kernel) \
  74. if self.vm.kernel is not None \
  75. else os.path.join(self.vm.dir_path,
  76. qubes.config.vm_files['kernels_subdir'])
  77. @property
  78. def modules_img(self):
  79. '''Path to image with modules.
  80. Depending on domain, this may be global or inside domain's dir.
  81. '''
  82. return os.path.join(self.kernels_dir, 'modules.img')
  83. @property
  84. def modules_img_rw(self):
  85. ''':py:obj:`True` if module image should be mounted RW, :py:obj:`False`
  86. otherwise.'''
  87. return self.vm.kernel is None
  88. def abspath(self, path, rel=None):
  89. '''Make absolute path.
  90. If given path is relative, it is interpreted as relative to
  91. :py:attr:`self.vm.dir_path` or given *rel*.
  92. '''
  93. return path if os.path.isabs(path) \
  94. else os.path.join(rel or self.vm.dir_path, path)
  95. def get_config_params(self):
  96. raise NotImplementedError()
  97. @staticmethod
  98. def _copy_file(source, destination):
  99. '''Effective file copy, preserving sparse files etc.
  100. '''
  101. # TODO: Windows support
  102. # We prefer to use Linux's cp, because it nicely handles sparse files
  103. try:
  104. subprocess.check_call(['cp', source, destination])
  105. except subprocess.CalledProcessError:
  106. raise IOError('Error while copying {!r} to {!r}'.format(
  107. source, destination))
  108. def get_disk_utilization(self):
  109. return qubes.utils.get_disk_usage(self.vm.dir_path)
  110. def get_disk_utilization_private_img(self):
  111. # pylint: disable=invalid-name
  112. return qubes.utils.get_disk_usage(self.private_img)
  113. def get_private_img_sz(self):
  114. if not os.path.exists(self.private_img):
  115. return 0
  116. return os.path.getsize(self.private_img)
  117. def resize_private_img(self, size):
  118. raise NotImplementedError()
  119. def create_on_disk_private_img(self, source_template=None):
  120. raise NotImplementedError()
  121. def create_on_disk_root_img(self, source_template=None):
  122. raise NotImplementedError()
  123. def create_on_disk(self, source_template=None):
  124. if source_template is None:
  125. source_template = self.vm.template
  126. old_umask = os.umask(002)
  127. self.vm.log.info('Creating directory: {0}'.format(self.vm.dir_path))
  128. os.mkdir(self.vm.dir_path)
  129. self.create_on_disk_private_img(source_template)
  130. self.create_on_disk_root_img(source_template)
  131. self.reset_volatile_storage(source_template)
  132. os.umask(old_umask)
  133. def clone_disk_files(self, src_vm):
  134. self.vm.log.info('Creating directory: {0}'.format(self.vm.dir_path))
  135. os.mkdir(self.vm.dir_path)
  136. if hasattr(src_vm, 'private_img'):
  137. self.vm.log.info('Copying the private image: {} -> {}'.format(
  138. src_vm.private_img, self.vm.private_img))
  139. self._copy_file(src_vm.private_img, self.vm.private_img)
  140. if src_vm.updateable and hasattr(src_vm, 'root_img'):
  141. self.vm.log.info('Copying the root image: {} -> {}'.format(
  142. src_vm.root_img, self.root_img))
  143. self._copy_file(src_vm.root_img, self.root_img)
  144. # TODO: modules?
  145. # XXX which modules? -woju
  146. @staticmethod
  147. def rename(newpath, oldpath):
  148. '''Move storage directory, most likely during domain's rename.
  149. .. note::
  150. The arguments are in different order than in :program:`cp` utility.
  151. .. versionchange:: 3.0
  152. This is now dummy method that just passes everything to
  153. :py:func:`os.rename`.
  154. :param str newpath: New path
  155. :param str oldpath: Old path
  156. '''
  157. os.rename(oldpath, newpath)
  158. def verify_files(self):
  159. if not os.path.exists(self.vm.dir_path):
  160. raise qubes.QubesException(
  161. 'VM directory does not exist: {}'.format(self.vm.dir_path))
  162. if hasattr(self.vm, 'root_img') and not os.path.exists(self.root_img):
  163. raise qubes.QubesException(
  164. 'VM root image file does not exist: {}'.format(self.root_img))
  165. if hasattr(self.vm, 'private_img') \
  166. and not os.path.exists(self.private_img):
  167. raise qubes.QubesException(
  168. 'VM private image file does not exist: {}'.format(
  169. self.private_img))
  170. if self.modules_img is not None \
  171. and not os.path.exists(self.modules_img):
  172. raise qubes.QubesException(
  173. 'VM kernel modules image does not exists: {}'.format(
  174. self.modules_img))
  175. def remove_from_disk(self):
  176. shutil.rmtree(self.vm.dir_path)
  177. def reset_volatile_storage(self, source_template=None):
  178. if source_template is None:
  179. source_template = self.vm.template
  180. # Re-create only for template based VMs
  181. if source_template is not None and self.volatile_img:
  182. if os.path.exists(self.volatile_img):
  183. os.remove(self.volatile_img)
  184. # For StandaloneVM create it only if not already exists
  185. # (eg after backup-restore)
  186. if hasattr(self.vm, 'volatile_img') \
  187. and not os.path.exists(self.vm.volatile_img):
  188. self.vm.log.info(
  189. 'Creating volatile image: {0}'.format(self.volatile_img))
  190. subprocess.check_call(
  191. [qubes.config.system_path["prepare_volatile_img_cmd"],
  192. self.volatile_img,
  193. str(self.root_img_size / 1024 / 1024)])
  194. def prepare_for_vm_startup(self):
  195. self.reset_volatile_storage()
  196. if hasattr(self.vm, 'private_img') \
  197. and not os.path.exists(self.private_img):
  198. self.vm.log.info('Creating empty VM private image file: {0}'.format(
  199. self.private_img))
  200. self.create_on_disk_private_img()
  201. def get_storage(vm):
  202. '''Factory yielding storage class instances for domains.
  203. :raises ImportError: when storage class specified in config cannot be found
  204. :raises KeyError: when storage class specified in config cannot be found
  205. '''
  206. pkg, cls = qubes.config.defaults['storage_class'].strip().rsplit('.', 1)
  207. # this may raise ImportError or KeyError, that's okay
  208. return importlib.import_module(pkg).__dict__[cls](vm)