qubesutils.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822
  1. #!/usr/bin/python
  2. # -*- coding: utf-8 -*-
  3. #
  4. # The Qubes OS Project, http://www.qubes-os.org
  5. #
  6. # Copyright (C) 2011 Marek Marczykowski <marmarek@invisiblethingslab.com>
  7. # Copyright (C) 2014 Wojciech Porczyk <wojciech@porczyk.eu>
  8. #
  9. # This program is free software; you can redistribute it and/or
  10. # modify it under the terms of the GNU General Public License
  11. # as published by the Free Software Foundation; either version 2
  12. # of the License, or (at your option) any later version.
  13. #
  14. # This program is distributed in the hope that it will be useful,
  15. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. # GNU General Public License for more details.
  18. #
  19. # You should have received a copy of the GNU General Public License
  20. # along with this program; if not, write to the Free Software
  21. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  22. #
  23. #
  24. from __future__ import absolute_import
  25. import string
  26. import errno
  27. from lxml import etree
  28. from lxml.etree import ElementTree, SubElement, Element
  29. from qubes.qubes import QubesException
  30. from qubes.qubes import vmm,defaults
  31. from qubes.qubes import system_path,vm_files
  32. import sys
  33. import os
  34. import subprocess
  35. import re
  36. import time
  37. import stat
  38. import libvirt
  39. from qubes.qdb import QubesDB,Error,DisconnectedError
  40. import xen.lowlevel.xc
  41. import xen.lowlevel.xs
  42. # all frontends, prefer xvdi
  43. # TODO: get this from libvirt driver?
  44. AVAILABLE_FRONTENDS = ['xvd'+c for c in
  45. string.lowercase[8:]+string.lowercase[:8]]
  46. def mbytes_to_kmg(size):
  47. if size > 1024:
  48. return "%d GiB" % (size/1024)
  49. else:
  50. return "%d MiB" % size
  51. def kbytes_to_kmg(size):
  52. if size > 1024:
  53. return mbytes_to_kmg(size/1024)
  54. else:
  55. return "%d KiB" % size
  56. def bytes_to_kmg(size):
  57. if size > 1024:
  58. return kbytes_to_kmg(size/1024)
  59. else:
  60. return "%d B" % size
  61. def size_to_human (size):
  62. """Humane readable size, with 1/10 precission"""
  63. if size < 1024:
  64. return str (size);
  65. elif size < 1024*1024:
  66. return str(round(size/1024.0,1)) + ' KiB'
  67. elif size < 1024*1024*1024:
  68. return str(round(size/(1024.0*1024),1)) + ' MiB'
  69. else:
  70. return str(round(size/(1024.0*1024*1024),1)) + ' GiB'
  71. def print_stdout(text):
  72. print (text)
  73. def print_stderr(text):
  74. print >> sys.stderr, (text)
  75. ###### Block devices ########
  76. def block_devid_to_name(devid):
  77. major = devid / 256
  78. minor = devid % 256
  79. dev_class = ""
  80. if major == 202:
  81. dev_class = "xvd"
  82. elif major == 8:
  83. dev_class = "sd"
  84. else:
  85. raise QubesException("Unknown device class %d" % major)
  86. if minor % 16 == 0:
  87. return "%s%c" % (dev_class, ord('a')+minor/16)
  88. else:
  89. return "%s%c%d" % (dev_class, ord('a')+minor/16, minor%16)
  90. def block_name_to_majorminor(name):
  91. # check if it is already devid
  92. if isinstance(name, int):
  93. return (name / 256, name % 256)
  94. if name.isdigit():
  95. return (int(name) / 256, int(name) % 256)
  96. if os.path.exists('/dev/%s' % name):
  97. blk_info = os.stat(os.path.realpath('/dev/%s' % name))
  98. if stat.S_ISBLK(blk_info.st_mode):
  99. return (blk_info.st_rdev / 256, blk_info.st_rdev % 256)
  100. major = 0
  101. minor = 0
  102. dXpY_style = False
  103. disk = True
  104. if name.startswith("xvd"):
  105. major = 202
  106. elif name.startswith("sd"):
  107. major = 8
  108. elif name.startswith("mmcblk"):
  109. dXpY_style = True
  110. major = 179
  111. elif name.startswith("scd"):
  112. disk = False
  113. major = 11
  114. elif name.startswith("sr"):
  115. disk = False
  116. major = 11
  117. elif name.startswith("loop"):
  118. dXpY_style = True
  119. disk = False
  120. major = 7
  121. elif name.startswith("md"):
  122. dXpY_style = True
  123. major = 9
  124. elif name.startswith("dm-"):
  125. disk = False
  126. major = 253
  127. else:
  128. # Unknown device
  129. return (0, 0)
  130. if not dXpY_style:
  131. name_match = re.match(r"^([a-z]+)([a-z-])([0-9]*)$", name)
  132. else:
  133. name_match = re.match(r"^([a-z]+)([0-9]*)(?:p([0-9]+))?$", name)
  134. if not name_match:
  135. raise QubesException("Invalid device name: %s" % name)
  136. if disk:
  137. if dXpY_style:
  138. minor = int(name_match.group(2))*8
  139. else:
  140. minor = (ord(name_match.group(2))-ord('a')) * 16
  141. else:
  142. minor = 0
  143. if name_match.group(3):
  144. minor += int(name_match.group(3))
  145. return (major, minor)
  146. def block_name_to_devid(name):
  147. # check if it is already devid
  148. if isinstance(name, int):
  149. return name
  150. if name.isdigit():
  151. return int(name)
  152. (major, minor) = block_name_to_majorminor(name)
  153. return major << 8 | minor
  154. def block_find_unused_frontend(vm = None):
  155. assert vm is not None
  156. assert vm.is_running()
  157. xml = vm.libvirt_domain.XMLDesc()
  158. parsed_xml = etree.fromstring(xml)
  159. used = [target.get('dev', None) for target in
  160. parsed_xml.xpath("//domain/devices/disk/target")]
  161. for dev in AVAILABLE_FRONTENDS:
  162. if dev not in used:
  163. return dev
  164. return None
  165. def block_list_vm(vm, system_disks = False):
  166. name_re = re.compile(r"^[a-z0-9-]{1,12}$")
  167. device_re = re.compile(r"^[a-z0-9/-]{1,64}$")
  168. # FIXME: any better idea of desc_re?
  169. desc_re = re.compile(r"^.{1,255}$")
  170. mode_re = re.compile(r"^[rw]$")
  171. assert vm is not None
  172. if not vm.is_running():
  173. return []
  174. devices_list = {}
  175. try:
  176. untrusted_devices = vm.qdb.multiread('/qubes-block-devices/')
  177. except Error:
  178. vm.refresh()
  179. return {}
  180. def get_dev_item(dev, item):
  181. return untrusted_devices.get(
  182. '/qubes-block-devices/%s/%s' % (dev, item),
  183. None)
  184. untrusted_devices_names = list(set(map(lambda x: x.split("/")[2],
  185. untrusted_devices.keys())))
  186. for untrusted_dev_name in untrusted_devices_names:
  187. if name_re.match(untrusted_dev_name):
  188. dev_name = untrusted_dev_name
  189. untrusted_device_size = get_dev_item(dev_name, 'size')
  190. untrusted_device_desc = get_dev_item(dev_name, 'desc')
  191. untrusted_device_mode = get_dev_item(dev_name, 'mode')
  192. untrusted_device_device = get_dev_item(dev_name, 'device')
  193. if untrusted_device_desc is None or untrusted_device_mode is None\
  194. or untrusted_device_size is None:
  195. print >>sys.stderr, "Missing field in %s device parameters" %\
  196. dev_name
  197. continue
  198. if untrusted_device_device is None:
  199. untrusted_device_device = '/dev/' + dev_name
  200. if not device_re.match(untrusted_device_device):
  201. print >> sys.stderr, "Invalid %s device path in VM '%s'" % (
  202. dev_name, vm.name)
  203. continue
  204. device_device = untrusted_device_device
  205. if not untrusted_device_size.isdigit():
  206. print >> sys.stderr, "Invalid %s device size in VM '%s'" % (
  207. dev_name, vm.name)
  208. continue
  209. device_size = int(untrusted_device_size)
  210. if not desc_re.match(untrusted_device_desc):
  211. print >> sys.stderr, "Invalid %s device desc in VM '%s'" % (
  212. dev_name, vm.name)
  213. continue
  214. device_desc = untrusted_device_desc
  215. if not mode_re.match(untrusted_device_mode):
  216. print >> sys.stderr, "Invalid %s device mode in VM '%s'" % (
  217. dev_name, vm.name)
  218. continue
  219. device_mode = untrusted_device_mode
  220. if not system_disks:
  221. if vm.qid == 0 and device_desc.startswith(system_path[
  222. "qubes_base_dir"]):
  223. continue
  224. visible_name = "%s:%s" % (vm.name, dev_name)
  225. devices_list[visible_name] = {
  226. "name": visible_name,
  227. "vm": vm.name,
  228. "device": device_device,
  229. "size": device_size,
  230. "desc": device_desc,
  231. "mode": device_mode
  232. }
  233. return devices_list
  234. def block_list(qvmc = None, vm = None, system_disks = False):
  235. if vm is not None:
  236. if not vm.is_running():
  237. return []
  238. else:
  239. vm_list = [ vm ]
  240. else:
  241. if qvmc is None:
  242. raise QubesException("You must pass either qvm or vm argument")
  243. vm_list = qvmc.values()
  244. devices_list = {}
  245. for vm in vm_list:
  246. devices_list.update(block_list_vm(vm, system_disks))
  247. return devices_list
  248. def block_check_attached(qvmc, device):
  249. """
  250. @type qvmc: QubesVmCollection
  251. """
  252. if qvmc is None:
  253. # TODO: ValueError
  254. raise QubesException("You need to pass qvmc argument")
  255. for vm in qvmc.values():
  256. if vm.qid == 0:
  257. # Connecting devices to dom0 not supported
  258. continue
  259. if not vm.is_running():
  260. continue
  261. try:
  262. libvirt_domain = vm.libvirt_domain
  263. if libvirt_domain:
  264. xml = libvirt_domain.XMLDesc()
  265. else:
  266. xml = None
  267. except libvirt.libvirtError:
  268. if vmm.libvirt_conn.virConnGetLastError()[0] == libvirt.VIR_ERR_NO_DOMAIN:
  269. xml = None
  270. else:
  271. raise
  272. if xml:
  273. parsed_xml = etree.fromstring(xml)
  274. disks = parsed_xml.xpath("//domain/devices/disk")
  275. for disk in disks:
  276. backend_name = 'dom0'
  277. if disk.find('backenddomain') is not None:
  278. backend_name = disk.find('backenddomain').get('name')
  279. source = disk.find('source')
  280. if disk.get('type') == 'file':
  281. path = source.get('file')
  282. elif disk.get('type') == 'block':
  283. path = source.get('dev')
  284. else:
  285. # TODO: logger
  286. print >>sys.stderr, "Unknown disk type '%s' attached to " \
  287. "VM '%s'" % (source.get('type'),
  288. vm.name)
  289. continue
  290. if backend_name == device['vm'] and path == device['device']:
  291. return {
  292. "frontend": disk.find('target').get('dev'),
  293. "vm": vm}
  294. return None
  295. def device_attach_check(vm, backend_vm, device, frontend, mode):
  296. """ Checks all the parameters, dies on errors """
  297. if not vm.is_running():
  298. raise QubesException("VM %s not running" % vm.name)
  299. if not backend_vm.is_running():
  300. raise QubesException("VM %s not running" % backend_vm.name)
  301. if device['mode'] == 'r' and mode == 'w':
  302. raise QubesException("Cannot attach read-only device in read-write "
  303. "mode")
  304. def block_attach(qvmc, vm, device, frontend=None, mode="w", auto_detach=False, wait=True):
  305. backend_vm = qvmc.get_vm_by_name(device['vm'])
  306. device_attach_check(vm, backend_vm, device, frontend, mode)
  307. if frontend is None:
  308. frontend = block_find_unused_frontend(vm)
  309. if frontend is None:
  310. raise QubesException("No unused frontend found")
  311. else:
  312. # Check if any device attached at this frontend
  313. xml = vm.libvirt_domain.XMLDesc()
  314. parsed_xml = etree.fromstring(xml)
  315. disks = parsed_xml.xpath("//domain/devices/disk/target[@dev='%s']" %
  316. frontend)
  317. if len(disks):
  318. raise QubesException("Frontend %s busy in VM %s, detach it first" % (frontend, vm.name))
  319. # Check if this device is attached to some domain
  320. attached_vm = block_check_attached(qvmc, device)
  321. if attached_vm:
  322. if auto_detach:
  323. block_detach(attached_vm['vm'], attached_vm['frontend'])
  324. else:
  325. raise QubesException("Device %s from %s already connected to VM "
  326. "%s as %s" % (device['device'],
  327. backend_vm.name, attached_vm['vm'], attached_vm['frontend']))
  328. disk = Element("disk")
  329. disk.set('type', 'block')
  330. disk.set('device', 'disk')
  331. SubElement(disk, 'driver').set('name', 'phy')
  332. SubElement(disk, 'source').set('dev', device['device'])
  333. SubElement(disk, 'target').set('dev', frontend)
  334. if backend_vm.qid != 0:
  335. SubElement(disk, 'backenddomain').set('name', device['vm'])
  336. vm.libvirt_domain.attachDevice(etree.tostring(disk, encoding='utf-8'))
  337. try:
  338. # trigger watches to update device status
  339. # FIXME: this should be removed once libvirt will report such
  340. # events itself
  341. vm.qdb.write('/qubes-block-devices', '')
  342. except Error:
  343. pass
  344. def block_detach(vm, frontend = "xvdi"):
  345. xml = vm.libvirt_domain.XMLDesc()
  346. parsed_xml = etree.fromstring(xml)
  347. attached = parsed_xml.xpath("//domain/devices/disk")
  348. for disk in attached:
  349. if frontend is not None and disk.find('target').get('dev') != frontend:
  350. # Not the device we are looking for
  351. continue
  352. if frontend is None:
  353. # ignore system disks
  354. if disk.find('domain') == None and \
  355. disk.find('source').get('dev').startswith(system_path[
  356. "qubes_base_dir"]):
  357. continue
  358. vm.libvirt_domain.detachDevice(etree.tostring(disk, encoding='utf-8'))
  359. try:
  360. # trigger watches to update device status
  361. # FIXME: this should be removed once libvirt will report such
  362. # events itself
  363. vm.qdb.write('/qubes-block-devices', '')
  364. except Error:
  365. pass
  366. def block_detach_all(vm):
  367. """ Detach all non-system devices"""
  368. block_detach(vm, None)
  369. ####### USB devices ######
  370. usb_ver_re = re.compile(r"^(1|2)$")
  371. usb_device_re = re.compile(r"^[0-9]+-[0-9]+(_[0-9]+)?$")
  372. usb_port_re = re.compile(r"^$|^[0-9]+-[0-9]+(\.[0-9]+)?$")
  373. usb_desc_re = re.compile(r"^[ -~]{1,255}$")
  374. # should match valid VM name
  375. usb_connected_to_re = re.compile(r"^[a-zA-Z][a-zA-Z0-9_.-]*$")
  376. def usb_decode_device_from_qdb(qdb_encoded_device):
  377. """ recover actual device name (xenstore doesn't allow dot in key names, so it was translated to underscore) """
  378. return qdb_encoded_device.replace('_', '.')
  379. def usb_encode_device_for_qdb(device):
  380. """ encode actual device name (xenstore doesn't allow dot in key names, so translated it into underscore) """
  381. return device.replace('.', '_')
  382. def usb_list_vm(qvmc, vm):
  383. if not vm.is_running():
  384. return {}
  385. try:
  386. untrusted_devices = vm.qdb.multiread('/qubes-usb-devices/')
  387. except Error:
  388. vm.refresh()
  389. return {}
  390. def get_dev_item(dev, item):
  391. return untrusted_devices.get(
  392. '/qubes-usb-devices/%s/%s' % (dev, item),
  393. None)
  394. devices = {}
  395. untrusted_devices_names = list(set(map(lambda x: x.split("/")[2],
  396. untrusted_devices.keys())))
  397. for untrusted_dev_name in untrusted_devices_names:
  398. if usb_device_re.match(untrusted_dev_name):
  399. dev_name = untrusted_dev_name
  400. untrusted_device_desc = get_dev_item(dev_name, 'desc')
  401. if not usb_desc_re.match(untrusted_device_desc):
  402. print >> sys.stderr, "Invalid %s device desc in VM '%s'" % (
  403. dev_name, vm.name)
  404. continue
  405. device_desc = untrusted_device_desc
  406. untrusted_connected_to = get_dev_item(dev_name, 'connected-to')
  407. if untrusted_connected_to:
  408. if not usb_connected_to_re.match(untrusted_connected_to):
  409. print >>sys.stderr, \
  410. "Invalid %s device 'connected-to' in VM '%s'" % (
  411. dev_name, vm.name)
  412. continue
  413. connected_to = qvmc.get_vm_by_name(untrusted_connected_to)
  414. if connected_to is None:
  415. print >>sys.stderr, \
  416. "Device {} appears to be connected to {}, " \
  417. "but such VM doesn't exist".format(
  418. dev_name, untrusted_connected_to)
  419. else:
  420. connected_to = None
  421. device = usb_decode_device_from_qdb(dev_name)
  422. full_name = vm.name + ':' + device
  423. devices[full_name] = {
  424. 'vm': vm,
  425. 'device': device,
  426. 'qdb_path': '/qubes-usb-devices/' + dev_name,
  427. 'name': full_name,
  428. 'desc': device_desc,
  429. 'connected-to': connected_to,
  430. }
  431. return devices
  432. def usb_list(qvmc, vm=None):
  433. """
  434. Returns a dictionary of USB devices (for PVUSB backends running in all VM).
  435. The dictionary is keyed by 'name' (see below), each element is a dictionary itself:
  436. vm = backend domain object
  437. device = device ID
  438. name = <backend-vm>:<device>
  439. desc = description
  440. """
  441. if vm is not None:
  442. if not vm.is_running():
  443. return {}
  444. else:
  445. vm_list = [vm]
  446. else:
  447. vm_list = qvmc.values()
  448. devices_list = {}
  449. for vm in vm_list:
  450. devices_list.update(usb_list_vm(qvmc, vm))
  451. return devices_list
  452. def usb_check_attached(qvmc, device):
  453. """Reread device attachment status"""
  454. vm = device['vm']
  455. untrusted_connected_to = vm.qdb.read(
  456. '{}/connected-to'.format(device['qdb_path']))
  457. if untrusted_connected_to:
  458. if not usb_connected_to_re.match(untrusted_connected_to):
  459. raise QubesException(
  460. "Invalid %s device 'connected-to' in VM '%s'" % (
  461. device['device'], vm.name))
  462. connected_to = qvmc.get_vm_by_name(untrusted_connected_to)
  463. if connected_to is None:
  464. print >>sys.stderr, \
  465. "Device {} appears to be connected to {}, " \
  466. "but such VM doesn't exist".format(
  467. device['device'], untrusted_connected_to)
  468. else:
  469. connected_to = None
  470. return connected_to
  471. def usb_attach(qvmc, vm, device, auto_detach=False, wait=True):
  472. if not vm.is_running():
  473. raise QubesException("VM {} not running".format(vm.name))
  474. if not device['vm'].is_running():
  475. raise QubesException("VM {} not running".format(device['vm'].name))
  476. connected_to = usb_check_attached(qvmc, device)
  477. if connected_to:
  478. if auto_detach:
  479. usb_detach(qvmc, device)
  480. else:
  481. raise QubesException("Device {} already connected, to {}".format(
  482. device['name'], connected_to
  483. ))
  484. # set qrexec policy to allow this device
  485. policy_line = '{} {} allow\n'.format(vm.name, device['vm'].name)
  486. policy_path = '/etc/qubes-rpc/policy/qubes.USB+{}'.format(device['device'])
  487. policy_exists = os.path.exists(policy_path)
  488. if not policy_exists:
  489. try:
  490. fd = os.open(policy_path, os.O_CREAT | os.O_EXCL | os.O_WRONLY)
  491. with os.fdopen(fd, 'w') as f:
  492. f.write(policy_line)
  493. except OSError as e:
  494. if e.errno == errno.EEXIST:
  495. pass
  496. else:
  497. raise
  498. else:
  499. with open(policy_path, 'r+') as f:
  500. policy = f.readlines()
  501. policy.insert(0, policy_line)
  502. f.truncate(0)
  503. f.seek(0)
  504. f.write(''.join(policy))
  505. try:
  506. # and actual attach
  507. p = vm.run_service('qubes.USBAttach', passio_popen=True, user='root')
  508. (stdout, stderr) = p.communicate(
  509. '{} {}\n'.format(device['vm'].name, device['device']))
  510. if p.returncode != 0:
  511. # TODO: sanitize and include stdout
  512. raise QubesException('Device attach failed')
  513. finally:
  514. # FIXME: there is a race condition here - some other process might
  515. # modify the file in the meantime. This may result in unexpected
  516. # denials, but will not allow too much
  517. if not policy_exists:
  518. os.unlink(policy_path)
  519. else:
  520. with open(policy_path, 'r+') as f:
  521. policy = f.readlines()
  522. policy.remove('{} {} allow\n'.format(vm.name, device['vm'].name))
  523. f.truncate(0)
  524. f.seek(0)
  525. f.write(''.join(policy))
  526. def usb_detach(qvmc, vm, device):
  527. connected_to = usb_check_attached(qvmc, device)
  528. # detect race conditions; there is still race here, but much smaller
  529. if connected_to is None or connected_to.qid != vm.qid:
  530. raise QubesException(
  531. "Device {} not connected to VM {}".format(
  532. device['name'], vm.name))
  533. p = device['vm'].run_service('qubes.USBDetach', passio_popen=True,
  534. user='root')
  535. (stdout, stderr) = p.communicate(
  536. '{}\n'.format(device['device']))
  537. if p.returncode != 0:
  538. # TODO: sanitize and include stdout
  539. raise QubesException('Device detach failed')
  540. def usb_detach_all(qvmc, vm):
  541. for dev in usb_list(qvmc).values():
  542. connected_to = dev['connected-to']
  543. if connected_to is not None and connected_to.qid == vm.qid:
  544. usb_detach(qvmc, connected_to, dev)
  545. ####### QubesWatch ######
  546. def only_in_first_list(l1, l2):
  547. ret=[]
  548. for i in l1:
  549. if not i in l2:
  550. ret.append(i)
  551. return ret
  552. class QubesWatch(object):
  553. def __init__(self):
  554. self._qdb = {}
  555. self._qdb_events = {}
  556. self.block_callback = None
  557. self.meminfo_callback = None
  558. self.domain_callback = None
  559. libvirt.virEventRegisterDefaultImpl()
  560. # open new libvirt connection because above
  561. # virEventRegisterDefaultImpl is in practice effective only for new
  562. # connections
  563. self.libvirt_conn = libvirt.open(defaults['libvirt_uri'])
  564. self.libvirt_conn.domainEventRegisterAny(
  565. None,
  566. libvirt.VIR_DOMAIN_EVENT_ID_LIFECYCLE,
  567. self._domain_list_changed, None)
  568. self.libvirt_conn.domainEventRegisterAny(
  569. None,
  570. libvirt.VIR_DOMAIN_EVENT_ID_DEVICE_REMOVED,
  571. self._device_removed, None)
  572. # TODO: device attach libvirt event
  573. for vm in vmm.libvirt_conn.listAllDomains():
  574. try:
  575. if vm.isActive():
  576. self._register_watches(vm)
  577. except libvirt.libvirtError as e:
  578. # this will happen if we loose a race with another tool,
  579. # which can just remove the domain
  580. if e.get_error_code() == libvirt.VIR_ERR_NO_DOMAIN:
  581. pass
  582. raise
  583. # and for dom0
  584. self._register_watches(None)
  585. def _qdb_handler(self, watch, fd, events, domain_name):
  586. try:
  587. path = self._qdb[domain_name].read_watch()
  588. except DisconnectedError:
  589. libvirt.virEventRemoveHandle(watch)
  590. del(self._qdb_events[domain_name])
  591. self._qdb[domain_name].close()
  592. del(self._qdb[domain_name])
  593. return
  594. if path.startswith('/qubes-block-devices'):
  595. if self.block_callback is not None:
  596. self.block_callback(domain_name)
  597. def setup_block_watch(self, callback):
  598. self.block_callback = callback
  599. def setup_meminfo_watch(self, callback):
  600. raise NotImplementedError
  601. def setup_domain_watch(self, callback):
  602. self.domain_callback = callback
  603. def get_meminfo_key(self, xid):
  604. return '/local/domain/%s/memory/meminfo' % xid
  605. def _register_watches(self, libvirt_domain):
  606. if libvirt_domain:
  607. name = libvirt_domain.name()
  608. if name in self._qdb:
  609. return
  610. if not libvirt_domain.isActive():
  611. return
  612. # open separate connection to Qubes DB:
  613. # 1. to not confuse pull() with responses to real commands sent from
  614. # other threads (like read, write etc) with watch events
  615. # 2. to not think whether QubesDB is thread-safe (it isn't)
  616. try:
  617. self._qdb[name] = QubesDB(name)
  618. except Error as e:
  619. if e.args[0] != 2:
  620. raise
  621. libvirt.virEventAddTimeout(500, self._retry_register_watches,
  622. libvirt_domain)
  623. return
  624. else:
  625. name = "dom0"
  626. self._qdb[name] = QubesDB(name)
  627. try:
  628. self._qdb[name].watch('/qubes-block-devices')
  629. except Error as e:
  630. if e.args[0] == 102: # Connection reset by peer
  631. # QubesDB daemon not running - most likely we've connected to
  632. # stale daemon which just exited; retry later
  633. libvirt.virEventAddTimeout(500, self._retry_register_watches,
  634. libvirt_domain)
  635. return
  636. self._qdb_events[name] = libvirt.virEventAddHandle(
  637. self._qdb[name].watch_fd(),
  638. libvirt.VIR_EVENT_HANDLE_READABLE,
  639. self._qdb_handler, name)
  640. def _retry_register_watches(self, timer, libvirt_domain):
  641. libvirt.virEventRemoveTimeout(timer)
  642. self._register_watches(libvirt_domain)
  643. def _unregister_watches(self, libvirt_domain):
  644. name = libvirt_domain.name()
  645. if name in self._qdb_events:
  646. libvirt.virEventRemoveHandle(self._qdb_events[name])
  647. del(self._qdb_events[name])
  648. if name in self._qdb:
  649. self._qdb[name].close()
  650. del(self._qdb[name])
  651. def _domain_list_changed(self, conn, domain, event, reason, param):
  652. # use VIR_DOMAIN_EVENT_RESUMED instead of VIR_DOMAIN_EVENT_STARTED to
  653. # make sure that qubesdb daemon is already running
  654. if event == libvirt.VIR_DOMAIN_EVENT_RESUMED:
  655. self._register_watches(domain)
  656. elif event == libvirt.VIR_DOMAIN_EVENT_STOPPED:
  657. self._unregister_watches(domain)
  658. else:
  659. # ignore other events for now
  660. return None
  661. if self.domain_callback:
  662. self.domain_callback(name=domain.name(), uuid=domain.UUID())
  663. def _device_removed(self, conn, domain, device, param):
  664. if self.block_callback is not None:
  665. self.block_callback(domain.name())
  666. def watch_loop(self):
  667. while True:
  668. libvirt.virEventRunDefaultImpl()
  669. ##### updates check #####
  670. #
  671. # XXX this whole section is a new global property
  672. # TODO make event handlers
  673. #
  674. UPDATES_DOM0_DISABLE_FLAG='/var/lib/qubes/updates/disable-updates'
  675. UPDATES_DEFAULT_VM_DISABLE_FLAG=\
  676. '/var/lib/qubes/updates/vm-default-disable-updates'
  677. def updates_vms_toggle(qvm_collection, value):
  678. # Flag for new VMs
  679. if value:
  680. if os.path.exists(UPDATES_DEFAULT_VM_DISABLE_FLAG):
  681. os.unlink(UPDATES_DEFAULT_VM_DISABLE_FLAG)
  682. else:
  683. open(UPDATES_DEFAULT_VM_DISABLE_FLAG, "w").close()
  684. # Change for existing VMs
  685. for vm in qvm_collection.values():
  686. if vm.qid == 0:
  687. continue
  688. if value:
  689. vm.services.pop('qubes-update-check', None)
  690. if vm.is_running():
  691. try:
  692. vm.run("systemctl start qubes-update-check.timer",
  693. user="root")
  694. except:
  695. pass
  696. else:
  697. vm.services['qubes-update-check'] = False
  698. if vm.is_running():
  699. try:
  700. vm.run("systemctl stop qubes-update-check.timer",
  701. user="root")
  702. except:
  703. pass
  704. def updates_dom0_toggle(qvm_collection, value):
  705. if value:
  706. if os.path.exists(UPDATES_DOM0_DISABLE_FLAG):
  707. os.unlink(UPDATES_DOM0_DISABLE_FLAG)
  708. else:
  709. open(UPDATES_DOM0_DISABLE_FLAG, "w").close()
  710. def updates_dom0_status(qvm_collection):
  711. return not os.path.exists(UPDATES_DOM0_DISABLE_FLAG)
  712. def updates_vms_status(qvm_collection):
  713. # default value:
  714. status = not os.path.exists(UPDATES_DEFAULT_VM_DISABLE_FLAG)
  715. # check if all the VMs uses the default value
  716. for vm in qvm_collection.values():
  717. if vm.qid == 0:
  718. continue
  719. if vm.services.get('qubes-update-check', True) != status:
  720. # "mixed"
  721. return None
  722. return status
  723. # vim:sw=4:et: