qubesutils.py 29 KB

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