qubesutils.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844
  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. or not path.startswith('/dev/') and path == device[
  294. 'desc']):
  295. return {
  296. "frontend": disk.find('target').get('dev'),
  297. "vm": vm}
  298. return None
  299. def device_attach_check(vm, backend_vm, device, frontend, mode):
  300. """ Checks all the parameters, dies on errors """
  301. if not vm.is_running():
  302. raise QubesException("VM %s not running" % vm.name)
  303. if not backend_vm.is_running():
  304. raise QubesException("VM %s not running" % backend_vm.name)
  305. if device['mode'] == 'r' and mode == 'w':
  306. raise QubesException("Cannot attach read-only device in read-write "
  307. "mode")
  308. def block_attach(qvmc, vm, device, frontend=None, mode="w", auto_detach=False, wait=True):
  309. backend_vm = qvmc.get_vm_by_name(device['vm'])
  310. device_attach_check(vm, backend_vm, device, frontend, mode)
  311. if frontend is None:
  312. frontend = block_find_unused_frontend(vm)
  313. if frontend is None:
  314. raise QubesException("No unused frontend found")
  315. else:
  316. # Check if any device attached at this frontend
  317. xml = vm.libvirt_domain.XMLDesc()
  318. parsed_xml = etree.fromstring(xml)
  319. disks = parsed_xml.xpath("//domain/devices/disk/target[@dev='%s']" %
  320. frontend)
  321. if len(disks):
  322. raise QubesException("Frontend %s busy in VM %s, detach it first" % (frontend, vm.name))
  323. # Check if this device is attached to some domain
  324. attached_vm = block_check_attached(qvmc, device)
  325. if attached_vm:
  326. if auto_detach:
  327. block_detach(attached_vm['vm'], attached_vm['frontend'])
  328. else:
  329. raise QubesException("Device %s from %s already connected to VM "
  330. "%s as %s" % (device['device'],
  331. backend_vm.name, attached_vm['vm'], attached_vm['frontend']))
  332. disk = Element("disk")
  333. disk.set('type', 'block')
  334. disk.set('device', 'disk')
  335. SubElement(disk, 'driver').set('name', 'phy')
  336. SubElement(disk, 'source').set('dev', device['device'])
  337. SubElement(disk, 'target').set('dev', frontend)
  338. if backend_vm.qid != 0:
  339. SubElement(disk, 'backenddomain').set('name', device['vm'])
  340. if mode == "r":
  341. SubElement(disk, 'readonly')
  342. vm.libvirt_domain.attachDevice(etree.tostring(disk, encoding='utf-8'))
  343. try:
  344. # trigger watches to update device status
  345. # FIXME: this should be removed once libvirt will report such
  346. # events itself
  347. vm.qdb.write('/qubes-block-devices', '')
  348. except Error:
  349. pass
  350. def block_detach(vm, frontend = "xvdi"):
  351. xml = vm.libvirt_domain.XMLDesc()
  352. parsed_xml = etree.fromstring(xml)
  353. attached = parsed_xml.xpath("//domain/devices/disk")
  354. for disk in attached:
  355. if frontend is not None and disk.find('target').get('dev') != frontend:
  356. # Not the device we are looking for
  357. continue
  358. if frontend is None:
  359. # ignore system disks
  360. if disk.find('domain') == None and \
  361. disk.find('source').get('dev').startswith(system_path[
  362. "qubes_base_dir"]):
  363. continue
  364. vm.libvirt_domain.detachDevice(etree.tostring(disk, encoding='utf-8'))
  365. try:
  366. # trigger watches to update device status
  367. # FIXME: this should be removed once libvirt will report such
  368. # events itself
  369. vm.qdb.write('/qubes-block-devices', '')
  370. except Error:
  371. pass
  372. def block_detach_all(vm):
  373. """ Detach all non-system devices"""
  374. block_detach(vm, None)
  375. ####### USB devices ######
  376. usb_ver_re = re.compile(r"^(1|2)$")
  377. usb_device_re = re.compile(r"^[0-9]+-[0-9]+(_[0-9]+)?$")
  378. usb_port_re = re.compile(r"^$|^[0-9]+-[0-9]+(\.[0-9]+)?$")
  379. usb_desc_re = re.compile(r"^[ -~]{1,255}$")
  380. # should match valid VM name
  381. usb_connected_to_re = re.compile(r"^[a-zA-Z][a-zA-Z0-9_.-]*$")
  382. def usb_decode_device_from_qdb(qdb_encoded_device):
  383. """ recover actual device name (xenstore doesn't allow dot in key names, so it was translated to underscore) """
  384. return qdb_encoded_device.replace('_', '.')
  385. def usb_encode_device_for_qdb(device):
  386. """ encode actual device name (xenstore doesn't allow dot in key names, so translated it into underscore) """
  387. return device.replace('.', '_')
  388. def usb_list_vm(qvmc, vm):
  389. if not vm.is_running():
  390. return {}
  391. try:
  392. untrusted_devices = vm.qdb.multiread('/qubes-usb-devices/')
  393. except Error:
  394. vm.refresh()
  395. return {}
  396. def get_dev_item(dev, item):
  397. return untrusted_devices.get(
  398. '/qubes-usb-devices/%s/%s' % (dev, item),
  399. None)
  400. devices = {}
  401. untrusted_devices_names = list(set(map(lambda x: x.split("/")[2],
  402. untrusted_devices.keys())))
  403. for untrusted_dev_name in untrusted_devices_names:
  404. if usb_device_re.match(untrusted_dev_name):
  405. dev_name = untrusted_dev_name
  406. untrusted_device_desc = get_dev_item(dev_name, 'desc')
  407. if not usb_desc_re.match(untrusted_device_desc):
  408. print >> sys.stderr, "Invalid %s device desc in VM '%s'" % (
  409. dev_name, vm.name)
  410. continue
  411. device_desc = untrusted_device_desc
  412. untrusted_connected_to = get_dev_item(dev_name, 'connected-to')
  413. if untrusted_connected_to:
  414. if not usb_connected_to_re.match(untrusted_connected_to):
  415. print >>sys.stderr, \
  416. "Invalid %s device 'connected-to' in VM '%s'" % (
  417. dev_name, vm.name)
  418. continue
  419. connected_to = qvmc.get_vm_by_name(untrusted_connected_to)
  420. if connected_to is None:
  421. print >>sys.stderr, \
  422. "Device {} appears to be connected to {}, " \
  423. "but such VM doesn't exist".format(
  424. dev_name, untrusted_connected_to)
  425. else:
  426. connected_to = None
  427. device = usb_decode_device_from_qdb(dev_name)
  428. full_name = vm.name + ':' + device
  429. devices[full_name] = {
  430. 'vm': vm,
  431. 'device': device,
  432. 'qdb_path': '/qubes-usb-devices/' + dev_name,
  433. 'name': full_name,
  434. 'desc': device_desc,
  435. 'connected-to': connected_to,
  436. }
  437. return devices
  438. def usb_list(qvmc, vm=None):
  439. """
  440. Returns a dictionary of USB devices (for PVUSB backends running in all VM).
  441. The dictionary is keyed by 'name' (see below), each element is a dictionary itself:
  442. vm = backend domain object
  443. device = device ID
  444. name = <backend-vm>:<device>
  445. desc = description
  446. """
  447. if vm is not None:
  448. if not vm.is_running():
  449. return {}
  450. else:
  451. vm_list = [vm]
  452. else:
  453. vm_list = qvmc.values()
  454. devices_list = {}
  455. for vm in vm_list:
  456. devices_list.update(usb_list_vm(qvmc, vm))
  457. return devices_list
  458. def usb_check_attached(qvmc, device):
  459. """Reread device attachment status"""
  460. vm = device['vm']
  461. untrusted_connected_to = vm.qdb.read(
  462. '{}/connected-to'.format(device['qdb_path']))
  463. if untrusted_connected_to:
  464. if not usb_connected_to_re.match(untrusted_connected_to):
  465. raise QubesException(
  466. "Invalid %s device 'connected-to' in VM '%s'" % (
  467. device['device'], vm.name))
  468. connected_to = qvmc.get_vm_by_name(untrusted_connected_to)
  469. if connected_to is None:
  470. print >>sys.stderr, \
  471. "Device {} appears to be connected to {}, " \
  472. "but such VM doesn't exist".format(
  473. device['device'], untrusted_connected_to)
  474. else:
  475. connected_to = None
  476. return connected_to
  477. def usb_attach(qvmc, vm, device, auto_detach=False, wait=True):
  478. if not vm.is_running():
  479. raise QubesException("VM {} not running".format(vm.name))
  480. if not device['vm'].is_running():
  481. raise QubesException("VM {} not running".format(device['vm'].name))
  482. connected_to = usb_check_attached(qvmc, device)
  483. if connected_to:
  484. if auto_detach:
  485. usb_detach(qvmc, device)
  486. else:
  487. raise QubesException("Device {} already connected, to {}".format(
  488. device['name'], connected_to
  489. ))
  490. # set qrexec policy to allow this device
  491. policy_line = '{} {} allow\n'.format(vm.name, device['vm'].name)
  492. policy_path = '/etc/qubes-rpc/policy/qubes.USB+{}'.format(device['device'])
  493. policy_exists = os.path.exists(policy_path)
  494. if not policy_exists:
  495. try:
  496. fd = os.open(policy_path, os.O_CREAT | os.O_EXCL | os.O_WRONLY)
  497. with os.fdopen(fd, 'w') as f:
  498. f.write(policy_line)
  499. except OSError as e:
  500. if e.errno == errno.EEXIST:
  501. pass
  502. else:
  503. raise
  504. else:
  505. with open(policy_path, 'r+') as f:
  506. policy = f.readlines()
  507. policy.insert(0, policy_line)
  508. f.truncate(0)
  509. f.seek(0)
  510. f.write(''.join(policy))
  511. try:
  512. # and actual attach
  513. p = vm.run_service('qubes.USBAttach', passio_popen=True, user='root')
  514. (stdout, stderr) = p.communicate(
  515. '{} {}\n'.format(device['vm'].name, device['device']))
  516. if p.returncode == 127:
  517. raise USBProxyNotInstalled(
  518. "qubes-usb-proxy not installed in the VM")
  519. elif p.returncode != 0:
  520. # TODO: sanitize and include stdout
  521. sanitized_stderr = ''.join([c for c in stderr if ord(c) >= 0x20])
  522. raise QubesException('Device attach failed: {}'.format(
  523. sanitized_stderr))
  524. finally:
  525. # FIXME: there is a race condition here - some other process might
  526. # modify the file in the meantime. This may result in unexpected
  527. # denials, but will not allow too much
  528. if not policy_exists:
  529. os.unlink(policy_path)
  530. else:
  531. with open(policy_path, 'r+') as f:
  532. policy = f.readlines()
  533. policy.remove('{} {} allow\n'.format(vm.name, device['vm'].name))
  534. f.truncate(0)
  535. f.seek(0)
  536. f.write(''.join(policy))
  537. def usb_detach(qvmc, vm, device):
  538. connected_to = usb_check_attached(qvmc, device)
  539. # detect race conditions; there is still race here, but much smaller
  540. if connected_to is None or connected_to.qid != vm.qid:
  541. raise QubesException(
  542. "Device {} not connected to VM {}".format(
  543. device['name'], vm.name))
  544. p = device['vm'].run_service('qubes.USBDetach', passio_popen=True,
  545. user='root')
  546. (stdout, stderr) = p.communicate(
  547. '{}\n'.format(device['device']))
  548. if p.returncode != 0:
  549. # TODO: sanitize and include stdout
  550. raise QubesException('Device detach failed')
  551. def usb_detach_all(qvmc, vm):
  552. for dev in usb_list(qvmc).values():
  553. connected_to = dev['connected-to']
  554. if connected_to is not None and connected_to.qid == vm.qid:
  555. usb_detach(qvmc, connected_to, dev)
  556. ####### QubesWatch ######
  557. def only_in_first_list(l1, l2):
  558. ret=[]
  559. for i in l1:
  560. if not i in l2:
  561. ret.append(i)
  562. return ret
  563. class QubesWatch(object):
  564. def __init__(self):
  565. self._qdb = {}
  566. self._qdb_events = {}
  567. self.block_callback = None
  568. self.meminfo_callback = None
  569. self.domain_callback = None
  570. libvirt.virEventRegisterDefaultImpl()
  571. # open new libvirt connection because above
  572. # virEventRegisterDefaultImpl is in practice effective only for new
  573. # connections
  574. self.libvirt_conn = libvirt.open(defaults['libvirt_uri'])
  575. self.libvirt_conn.domainEventRegisterAny(
  576. None,
  577. libvirt.VIR_DOMAIN_EVENT_ID_LIFECYCLE,
  578. self._domain_list_changed, None)
  579. self.libvirt_conn.domainEventRegisterAny(
  580. None,
  581. libvirt.VIR_DOMAIN_EVENT_ID_DEVICE_REMOVED,
  582. self._device_removed, None)
  583. # TODO: device attach libvirt event
  584. for vm in vmm.libvirt_conn.listAllDomains():
  585. try:
  586. if vm.isActive():
  587. self._register_watches(vm)
  588. except libvirt.libvirtError as e:
  589. # this will happen if we loose a race with another tool,
  590. # which can just remove the domain
  591. if e.get_error_code() == libvirt.VIR_ERR_NO_DOMAIN:
  592. pass
  593. else:
  594. raise
  595. # and for dom0
  596. self._register_watches(None)
  597. def _qdb_handler(self, watch, fd, events, domain_name):
  598. try:
  599. path = self._qdb[domain_name].read_watch()
  600. except DisconnectedError:
  601. libvirt.virEventRemoveHandle(watch)
  602. del(self._qdb_events[domain_name])
  603. self._qdb[domain_name].close()
  604. del(self._qdb[domain_name])
  605. return
  606. if path.startswith('/qubes-block-devices'):
  607. if self.block_callback is not None:
  608. self.block_callback(domain_name)
  609. def setup_block_watch(self, callback):
  610. self.block_callback = callback
  611. def setup_meminfo_watch(self, callback):
  612. raise NotImplementedError
  613. def setup_domain_watch(self, callback):
  614. self.domain_callback = callback
  615. def get_meminfo_key(self, xid):
  616. return '/local/domain/%s/memory/meminfo' % xid
  617. def _register_watches(self, libvirt_domain):
  618. if libvirt_domain and libvirt_domain.ID() == 0:
  619. # don't use libvirt object for dom0, to always have the same
  620. # hardcoded "dom0" name
  621. libvirt_domain = None
  622. if libvirt_domain:
  623. name = libvirt_domain.name()
  624. if name in self._qdb:
  625. return
  626. if not libvirt_domain.isActive():
  627. return
  628. # open separate connection to Qubes DB:
  629. # 1. to not confuse pull() with responses to real commands sent from
  630. # other threads (like read, write etc) with watch events
  631. # 2. to not think whether QubesDB is thread-safe (it isn't)
  632. try:
  633. self._qdb[name] = QubesDB(name)
  634. except Error as e:
  635. if e.args[0] != 2:
  636. raise
  637. libvirt.virEventAddTimeout(500, self._retry_register_watches,
  638. libvirt_domain)
  639. return
  640. else:
  641. name = "dom0"
  642. if name in self._qdb:
  643. return
  644. self._qdb[name] = QubesDB(name)
  645. try:
  646. self._qdb[name].watch('/qubes-block-devices')
  647. except Error as e:
  648. if e.args[0] == 102: # Connection reset by peer
  649. # QubesDB daemon not running - most likely we've connected to
  650. # stale daemon which just exited; retry later
  651. libvirt.virEventAddTimeout(500, self._retry_register_watches,
  652. libvirt_domain)
  653. return
  654. self._qdb_events[name] = libvirt.virEventAddHandle(
  655. self._qdb[name].watch_fd(),
  656. libvirt.VIR_EVENT_HANDLE_READABLE,
  657. self._qdb_handler, name)
  658. def _retry_register_watches(self, timer, libvirt_domain):
  659. libvirt.virEventRemoveTimeout(timer)
  660. self._register_watches(libvirt_domain)
  661. def _unregister_watches(self, libvirt_domain):
  662. if libvirt_domain and libvirt_domain.ID() == 0:
  663. name = "dom0"
  664. else:
  665. name = libvirt_domain.name()
  666. if name in self._qdb_events:
  667. libvirt.virEventRemoveHandle(self._qdb_events[name])
  668. del(self._qdb_events[name])
  669. if name in self._qdb:
  670. self._qdb[name].close()
  671. del(self._qdb[name])
  672. def _domain_list_changed(self, conn, domain, event, reason, param):
  673. # use VIR_DOMAIN_EVENT_RESUMED instead of VIR_DOMAIN_EVENT_STARTED to
  674. # make sure that qubesdb daemon is already running
  675. if event == libvirt.VIR_DOMAIN_EVENT_RESUMED:
  676. self._register_watches(domain)
  677. elif event == libvirt.VIR_DOMAIN_EVENT_STOPPED:
  678. self._unregister_watches(domain)
  679. else:
  680. # ignore other events for now
  681. return None
  682. if self.domain_callback:
  683. self.domain_callback(name=domain.name(), uuid=domain.UUID())
  684. def _device_removed(self, conn, domain, device, param):
  685. if self.block_callback is not None:
  686. self.block_callback(domain.name())
  687. def watch_loop(self):
  688. while True:
  689. libvirt.virEventRunDefaultImpl()
  690. ##### updates check #####
  691. #
  692. # XXX this whole section is a new global property
  693. # TODO make event handlers
  694. #
  695. UPDATES_DOM0_DISABLE_FLAG='/var/lib/qubes/updates/disable-updates'
  696. UPDATES_DEFAULT_VM_DISABLE_FLAG=\
  697. '/var/lib/qubes/updates/vm-default-disable-updates'
  698. def updates_vms_toggle(qvm_collection, value):
  699. # Flag for new VMs
  700. if value:
  701. if os.path.exists(UPDATES_DEFAULT_VM_DISABLE_FLAG):
  702. os.unlink(UPDATES_DEFAULT_VM_DISABLE_FLAG)
  703. else:
  704. open(UPDATES_DEFAULT_VM_DISABLE_FLAG, "w").close()
  705. # Change for existing VMs
  706. for vm in qvm_collection.values():
  707. if vm.qid == 0:
  708. continue
  709. if value:
  710. vm.services.pop('qubes-update-check', None)
  711. if vm.is_running():
  712. try:
  713. vm.run("systemctl start qubes-update-check.timer",
  714. user="root")
  715. except:
  716. pass
  717. else:
  718. vm.services['qubes-update-check'] = False
  719. if vm.is_running():
  720. try:
  721. vm.run("systemctl stop qubes-update-check.timer",
  722. user="root")
  723. except:
  724. pass
  725. def updates_dom0_toggle(qvm_collection, value):
  726. if value:
  727. if os.path.exists(UPDATES_DOM0_DISABLE_FLAG):
  728. os.unlink(UPDATES_DOM0_DISABLE_FLAG)
  729. else:
  730. open(UPDATES_DOM0_DISABLE_FLAG, "w").close()
  731. def updates_dom0_status(qvm_collection):
  732. return not os.path.exists(UPDATES_DOM0_DISABLE_FLAG)
  733. def updates_vms_status(qvm_collection):
  734. # default value:
  735. status = not os.path.exists(UPDATES_DEFAULT_VM_DISABLE_FLAG)
  736. # check if all the VMs uses the default value
  737. for vm in qvm_collection.values():
  738. if vm.qid == 0:
  739. continue
  740. if vm.services.get('qubes-update-check', True) != status:
  741. # "mixed"
  742. return None
  743. return status
  744. # vim:sw=4:et: