internal.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. # -*- encoding: utf8 -*-
  2. #
  3. # The Qubes OS Project, http://www.qubes-os.org
  4. #
  5. # Copyright (C) 2017 Marek Marczykowski-Górecki
  6. # <marmarek@invisiblethingslab.com>
  7. #
  8. # This program is free software; you can redistribute it and/or modify
  9. # it under the terms of the GNU General Public License as published by
  10. # the Free Software Foundation; either version 2 of the License, or
  11. # (at your option) any later version.
  12. #
  13. # This program is distributed in the hope that it will be useful,
  14. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. # GNU General Public License for more details.
  17. #
  18. # You should have received a copy of the GNU General Public License along
  19. # with this program; if not, see <http://www.gnu.org/licenses/>.
  20. ''' Internal interface for dom0 components to communicate with qubesd. '''
  21. import asyncio
  22. import json
  23. import subprocess
  24. import qubes.api
  25. import qubes.api.admin
  26. import qubes.vm.adminvm
  27. import qubes.vm.dispvm
  28. class QubesInternalAPI(qubes.api.AbstractQubesAPI):
  29. ''' Communication interface for dom0 components,
  30. by design the input here is trusted.'''
  31. #
  32. # PRIVATE METHODS, not to be called via RPC
  33. #
  34. #
  35. # ACTUAL RPC CALLS
  36. #
  37. @qubes.api.method('internal.GetSystemInfo', no_payload=True)
  38. @asyncio.coroutine
  39. def getsysteminfo(self):
  40. assert self.dest.name == 'dom0'
  41. assert not self.arg
  42. system_info = {'domains': {
  43. domain.name: {
  44. 'tags': list(domain.tags),
  45. 'type': domain.__class__.__name__,
  46. 'dispvm_allowed': getattr(domain, 'dispvm_allowed', False),
  47. 'default_dispvm': (str(domain.default_dispvm) if
  48. getattr(domain, 'default_dispvm', None) else None),
  49. 'icon': str(domain.label.icon),
  50. } for domain in self.app.domains
  51. }}
  52. return json.dumps(system_info)
  53. @qubes.api.method('internal.vm.Start', no_payload=True)
  54. @asyncio.coroutine
  55. def start(self):
  56. assert not self.arg
  57. if self.dest.name == 'dom0':
  58. return
  59. yield from self.dest.start()
  60. @qubes.api.method('internal.vm.Create.DispVM', no_payload=True)
  61. @asyncio.coroutine
  62. def create_dispvm(self):
  63. assert not self.arg
  64. # TODO convert to coroutine
  65. dispvm = qubes.vm.dispvm.DispVM.from_appvm(self.dest)
  66. return dispvm.name
  67. @qubes.api.method('internal.vm.CleanupDispVM', no_payload=True)
  68. @asyncio.coroutine
  69. def cleanup_dispvm(self):
  70. assert not self.arg
  71. # TODO convert to coroutine
  72. self.dest.cleanup()
  73. @qubes.api.method('internal.vm.volume.ImportEnd')
  74. @asyncio.coroutine
  75. def vm_volume_import_end(self, untrusted_payload):
  76. '''
  77. This is second half of admin.vm.volume.Import handling. It is called
  78. when actual import is finished. Response from this method is sent do
  79. the client (as a response for admin.vm.volume.Import call).
  80. '''
  81. assert self.arg in self.dest.volumes.keys()
  82. success = untrusted_payload == b'ok'
  83. try:
  84. self.dest.storage.import_data_end(self.arg, success=success)
  85. except:
  86. self.dest.fire_event('domain-volume-import-end', volume=self.arg,
  87. succeess=False)
  88. raise
  89. self.dest.fire_event('domain-volume-import-end', volume=self.arg,
  90. succeess=success)
  91. if not success:
  92. raise qubes.exc.QubesException('Data import failed')
  93. @qubes.api.method('internal.SuspendPre', no_payload=True)
  94. @asyncio.coroutine
  95. def suspend_pre(self):
  96. '''
  97. Method called before host system goes to sleep.
  98. :return:
  99. '''
  100. # first notify all VMs
  101. processes = []
  102. for vm in self.app.domains:
  103. if isinstance(vm, qubes.vm.adminvm.AdminVM):
  104. continue
  105. if vm.is_running():
  106. proc = yield from vm.run_service(
  107. 'qubes.SuspendPreAll', user='root',
  108. stdin=subprocess.DEVNULL,
  109. stdout=subprocess.DEVNULL,
  110. stderr=subprocess.DEVNULL)
  111. processes.append(proc)
  112. # FIXME: some timeout?
  113. if processes:
  114. yield from asyncio.wait([p.wait() for p in processes])
  115. coros = []
  116. # then suspend/pause VMs
  117. for vm in self.app.domains:
  118. if isinstance(vm, qubes.vm.adminvm.AdminVM):
  119. continue
  120. if vm.is_running():
  121. coros.append(vm.suspend())
  122. if coros:
  123. yield from asyncio.wait(coros)
  124. @qubes.api.method('internal.SuspendPost', no_payload=True)
  125. @asyncio.coroutine
  126. def suspend_post(self):
  127. '''
  128. Method called after host system wake up from sleep.
  129. :return:
  130. '''
  131. coros = []
  132. # first resume/unpause VMs
  133. for vm in self.app.domains:
  134. if isinstance(vm, qubes.vm.adminvm.AdminVM):
  135. continue
  136. if vm.get_power_state() in ["Paused", "Suspended"]:
  137. coros.append(vm.resume())
  138. if coros:
  139. yield from asyncio.wait(coros)
  140. # then notify all VMs
  141. processes = []
  142. for vm in self.app.domains:
  143. if isinstance(vm, qubes.vm.adminvm.AdminVM):
  144. continue
  145. if vm.is_running():
  146. proc = yield from vm.run_service(
  147. 'qubes.SuspendPostAll', user='root',
  148. stdin=subprocess.DEVNULL,
  149. stdout=subprocess.DEVNULL,
  150. stderr=subprocess.DEVNULL)
  151. processes.append(proc)
  152. # FIXME: some timeout?
  153. if processes:
  154. yield from asyncio.wait([p.wait() for p in processes])