qubesutils.py 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837
  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 qubes import QubesException
  25. from qubes import xs, xl_ctx
  26. from qubes import system_path,vm_files
  27. import sys
  28. import os
  29. import subprocess
  30. import re
  31. import shutil
  32. import time
  33. import grp,pwd
  34. import stat
  35. from datetime import datetime
  36. from qmemman_client import QMemmanClient
  37. import xen.lowlevel.xc
  38. import xen.lowlevel.xl
  39. import xen.lowlevel.xs
  40. BLKSIZE = 512
  41. def mbytes_to_kmg(size):
  42. if size > 1024:
  43. return "%d GiB" % (size/1024)
  44. else:
  45. return "%d MiB" % size
  46. def kbytes_to_kmg(size):
  47. if size > 1024:
  48. return mbytes_to_kmg(size/1024)
  49. else:
  50. return "%d KiB" % size
  51. def bytes_to_kmg(size):
  52. if size > 1024:
  53. return kbytes_to_kmg(size/1024)
  54. else:
  55. return "%d B" % size
  56. def size_to_human (size):
  57. """Humane readable size, with 1/10 precission"""
  58. if size < 1024:
  59. return str (size);
  60. elif size < 1024*1024:
  61. return str(round(size/1024.0,1)) + ' KiB'
  62. elif size < 1024*1024*1024:
  63. return str(round(size/(1024.0*1024),1)) + ' MiB'
  64. else:
  65. return str(round(size/(1024.0*1024*1024),1)) + ' GiB'
  66. def parse_size(size):
  67. units = [ ('K', 1024), ('KB', 1024),
  68. ('M', 1024*1024), ('MB', 1024*1024),
  69. ('G', 1024*1024*1024), ('GB', 1024*1024*1024),
  70. ]
  71. size = size.strip().upper()
  72. if size.isdigit():
  73. return int(size)
  74. for unit, multiplier in units:
  75. if size.endswith(unit):
  76. size = size[:-len(unit)].strip()
  77. return int(size)*multiplier
  78. raise QubesException("Invalid size: {0}.".format(size))
  79. def get_disk_usage_one(st):
  80. try:
  81. return st.st_blocks * BLKSIZE
  82. except AttributeError:
  83. return st.st_size
  84. def get_disk_usage(path):
  85. try:
  86. st = os.lstat(path)
  87. except OSError:
  88. return 0
  89. ret = get_disk_usage_one(st)
  90. # if path is not a directory, this is skipped
  91. for dirpath, dirnames, filenames in os.walk(path):
  92. for name in dirnames + filenames:
  93. ret += get_disk_usage_one(os.lstat(os.path.join(dirpath, name)))
  94. return ret
  95. def print_stdout(text):
  96. print (text)
  97. def print_stderr(text):
  98. print >> sys.stderr, (text)
  99. ###### Block devices ########
  100. def block_devid_to_name(devid):
  101. major = devid / 256
  102. minor = devid % 256
  103. dev_class = ""
  104. if major == 202:
  105. dev_class = "xvd"
  106. elif major == 8:
  107. dev_class = "sd"
  108. else:
  109. raise QubesException("Unknown device class %d" % major)
  110. if minor % 16 == 0:
  111. return "%s%c" % (dev_class, ord('a')+minor/16)
  112. else:
  113. return "%s%c%d" % (dev_class, ord('a')+minor/16, minor%16)
  114. def block_name_to_majorminor(name):
  115. # check if it is already devid
  116. if isinstance(name, int):
  117. return (name / 256, name % 256)
  118. if name.isdigit():
  119. return (int(name) / 256, int(name) % 256)
  120. if os.path.exists('/dev/%s' % name):
  121. blk_info = os.stat(os.path.realpath('/dev/%s' % name))
  122. if stat.S_ISBLK(blk_info.st_mode):
  123. return (blk_info.st_rdev / 256, blk_info.st_rdev % 256)
  124. major = 0
  125. minor = 0
  126. dXpY_style = False
  127. disk = True
  128. if name.startswith("xvd"):
  129. major = 202
  130. elif name.startswith("sd"):
  131. major = 8
  132. elif name.startswith("mmcblk"):
  133. dXpY_style = True
  134. major = 179
  135. elif name.startswith("scd"):
  136. disk = False
  137. major = 11
  138. elif name.startswith("sr"):
  139. disk = False
  140. major = 11
  141. elif name.startswith("loop"):
  142. disk = False
  143. major = 7
  144. elif name.startswith("md"):
  145. dXpY_style = True
  146. major = 9
  147. elif name.startswith("dm-"):
  148. disk = False
  149. major = 253
  150. else:
  151. # Unknown device
  152. return (0, 0)
  153. if not dXpY_style:
  154. name_match = re.match(r"^([a-z]+)([a-z])([0-9]*)$", name)
  155. else:
  156. name_match = re.match(r"^([a-z]+)([0-9]*)(?:p([0-9]+))?$", name)
  157. if not name_match:
  158. raise QubesException("Invalid device name: %s" % name)
  159. if disk:
  160. if dXpY_style:
  161. minor = int(name_match.group(2))*8
  162. else:
  163. minor = (ord(name_match.group(2))-ord('a')) * 16
  164. else:
  165. minor = 0
  166. if name_match.group(3):
  167. minor += int(name_match.group(3))
  168. return (major, minor)
  169. def block_name_to_devid(name):
  170. # check if it is already devid
  171. if isinstance(name, int):
  172. return name
  173. if name.isdigit():
  174. return int(name)
  175. (major, minor) = block_name_to_majorminor(name)
  176. return major << 8 | minor
  177. def block_find_unused_frontend(vm = None):
  178. assert vm is not None
  179. assert vm.is_running()
  180. vbd_list = xs.ls('', '/local/domain/%d/device/vbd' % vm.xid)
  181. # xvd* devices
  182. major = 202
  183. # prefer xvdi
  184. for minor in range(8*16,254,16)+range(0,8*16,16):
  185. if vbd_list is None or str(major << 8 | minor) not in vbd_list:
  186. return block_devid_to_name(major << 8 | minor)
  187. return None
  188. def block_list(vm = None, system_disks = False):
  189. device_re = re.compile(r"^[a-z0-9]{1,12}$")
  190. # FIXME: any better idea of desc_re?
  191. desc_re = re.compile(r"^.{1,255}$")
  192. mode_re = re.compile(r"^[rw]$")
  193. xs_trans = xs.transaction_start()
  194. vm_list = []
  195. if vm is not None:
  196. if not vm.is_running():
  197. xs.transaction_end(xs_trans)
  198. return []
  199. else:
  200. vm_list = [ str(vm.xid) ]
  201. else:
  202. vm_list = xs.ls(xs_trans, '/local/domain')
  203. devices_list = {}
  204. for xid in vm_list:
  205. vm_name = xs.read(xs_trans, '/local/domain/%s/name' % xid)
  206. vm_devices = xs.ls(xs_trans, '/local/domain/%s/qubes-block-devices' % xid)
  207. if vm_devices is None:
  208. continue
  209. for device in vm_devices:
  210. # Sanitize device name
  211. if not device_re.match(device):
  212. print >> sys.stderr, "Invalid device name in VM '%s'" % vm_name
  213. continue
  214. device_size = xs.read(xs_trans, '/local/domain/%s/qubes-block-devices/%s/size' % (xid, device))
  215. device_desc = xs.read(xs_trans, '/local/domain/%s/qubes-block-devices/%s/desc' % (xid, device))
  216. device_mode = xs.read(xs_trans, '/local/domain/%s/qubes-block-devices/%s/mode' % (xid, device))
  217. if device_size is None or device_desc is None or device_mode is None:
  218. print >> sys.stderr, "Missing field in %s device parameters" % device
  219. continue
  220. if not device_size.isdigit():
  221. print >> sys.stderr, "Invalid %s device size in VM '%s'" % (device, vm_name)
  222. continue
  223. if not desc_re.match(device_desc):
  224. print >> sys.stderr, "Invalid %s device desc in VM '%s'" % (device, vm_name)
  225. continue
  226. if not mode_re.match(device_mode):
  227. print >> sys.stderr, "Invalid %s device mode in VM '%s'" % (device, vm_name)
  228. continue
  229. # Check if we know major number for this device; attach will work without this, but detach and check_attached don't
  230. if block_name_to_majorminor(device) == (0, 0):
  231. print >> sys.stderr, "Unsupported device %s:%s" % (vm_name, device)
  232. continue
  233. if not system_disks:
  234. if xid == '0' and device_desc.startswith(system_path["qubes_base_dir"]):
  235. continue
  236. visible_name = "%s:%s" % (vm_name, device)
  237. devices_list[visible_name] = {"name": visible_name, "xid":int(xid),
  238. "vm": vm_name, "device":device, "size":int(device_size),
  239. "desc":device_desc, "mode":device_mode}
  240. xs.transaction_end(xs_trans)
  241. return devices_list
  242. def block_check_attached(backend_vm, device, backend_xid = None):
  243. if backend_xid is None:
  244. backend_xid = backend_vm.xid
  245. xs_trans = xs.transaction_start()
  246. vm_list = xs.ls(xs_trans, '/local/domain/%d/backend/vbd' % backend_xid)
  247. if vm_list is None:
  248. xs.transaction_end(xs_trans)
  249. return None
  250. device_majorminor = None
  251. try:
  252. device_majorminor = block_name_to_majorminor(device)
  253. except:
  254. # Unknown devices will be compared directly - perhaps it is a filename?
  255. pass
  256. for vm_xid in vm_list:
  257. for devid in xs.ls(xs_trans, '/local/domain/%d/backend/vbd/%s' % (backend_xid, vm_xid)):
  258. (tmp_major, tmp_minor) = (0, 0)
  259. phys_device = xs.read(xs_trans, '/local/domain/%d/backend/vbd/%s/%s/physical-device' % (backend_xid, vm_xid, devid))
  260. dev_params = xs.read(xs_trans, '/local/domain/%d/backend/vbd/%s/%s/params' % (backend_xid, vm_xid, devid))
  261. if phys_device and phys_device.find(':'):
  262. (tmp_major, tmp_minor) = phys_device.split(":")
  263. tmp_major = int(tmp_major, 16)
  264. tmp_minor = int(tmp_minor, 16)
  265. else:
  266. # perhaps not ready yet - check params
  267. if not dev_params:
  268. # Skip not-phy devices
  269. continue
  270. elif not dev_params.startswith('/dev/'):
  271. # will compare params directly
  272. pass
  273. else:
  274. (tmp_major, tmp_minor) = block_name_to_majorminor(dev_params.lstrip('/dev/'))
  275. if (device_majorminor and (tmp_major, tmp_minor) == device_majorminor) or \
  276. (device_majorminor is None and dev_params == device):
  277. vm_name = xl_ctx.domid_to_name(int(vm_xid))
  278. frontend = block_devid_to_name(int(devid))
  279. xs.transaction_end(xs_trans)
  280. return {"xid":int(vm_xid), "frontend": frontend, "devid": int(devid), "vm": vm_name}
  281. xs.transaction_end(xs_trans)
  282. return None
  283. def block_attach(vm, backend_vm, device, frontend=None, mode="w", auto_detach=False, wait=True):
  284. device_attach_check(vm, backend_vm, device, frontend)
  285. do_block_attach(vm, backend_vm, device, frontend, mode, auto_detach, wait)
  286. def device_attach_check(vm, backend_vm, device, frontend):
  287. """ Checks all the parameters, dies on errors """
  288. if not vm.is_running():
  289. raise QubesException("VM %s not running" % vm.name)
  290. if not backend_vm.is_running():
  291. raise QubesException("VM %s not running" % backend_vm.name)
  292. def do_block_attach(vm, backend_vm, device, frontend, mode, auto_detach, wait):
  293. if frontend is None:
  294. frontend = block_find_unused_frontend(vm)
  295. if frontend is None:
  296. raise QubesException("No unused frontend found")
  297. else:
  298. # Check if any device attached at this frontend
  299. if xs.read('', '/local/domain/%d/device/vbd/%d/state' % (vm.xid, block_name_to_devid(frontend))) == '4':
  300. raise QubesException("Frontend %s busy in VM %s, detach it first" % (frontend, vm.name))
  301. # Check if this device is attached to some domain
  302. attached_vm = block_check_attached(backend_vm, device)
  303. if attached_vm:
  304. if auto_detach:
  305. block_detach(None, attached_vm['devid'], vm_xid=attached_vm['xid'])
  306. else:
  307. raise QubesException("Device %s from %s already connected to VM %s as %s" % (device, backend_vm.name, attached_vm['vm'], attached_vm['frontend']))
  308. if device.startswith('/'):
  309. backend_dev = 'script:file:' + device
  310. else:
  311. backend_dev = 'phy:/dev/' + device
  312. xl_cmd = [ '/usr/sbin/xl', 'block-attach', vm.name, backend_dev, frontend, mode, str(backend_vm.xid) ]
  313. subprocess.check_call(xl_cmd)
  314. if wait:
  315. be_path = '/local/domain/%d/backend/vbd/%d/%d' % (backend_vm.xid, vm.xid, block_name_to_devid(frontend))
  316. # There is no way to use xenstore watch with a timeout, so must check in a loop
  317. interval = 0.100
  318. # 5sec timeout
  319. timeout = 5/interval
  320. while timeout > 0:
  321. be_state = xs.read('', be_path + '/state')
  322. hotplug_state = xs.read('', be_path + '/hotplug-status')
  323. if be_state is None:
  324. raise QubesException("Backend device disappeared, something weird happened")
  325. elif int(be_state) == 4:
  326. # Ok
  327. return
  328. elif int(be_state) > 4:
  329. # Error
  330. error = xs.read('', '/local/domain/%d/error/backend/vbd/%d/%d/error' % (backend_vm.xid, vm.xid, block_name_to_devid(frontend)))
  331. if error is not None:
  332. raise QubesException("Error while connecting block device: " + error)
  333. else:
  334. raise QubesException("Unknown error while connecting block device")
  335. elif hotplug_state == 'error':
  336. hotplug_error = xs.read('', be_path + '/hotplug-error')
  337. if hotplug_error:
  338. raise QubesException("Error while connecting block device: " + hotplug_error)
  339. else:
  340. raise QubesException("Unknown hotplug error while connecting block device")
  341. time.sleep(interval)
  342. timeout -= interval
  343. raise QubesException("Timeout while waiting for block defice connection")
  344. def block_detach(vm, frontend = "xvdi", vm_xid = None):
  345. # Get XID if not provided already
  346. if vm_xid is None:
  347. if not vm.is_running():
  348. raise QubesException("VM %s not running" % vm.name)
  349. # FIXME: potential race
  350. vm_xid = vm.xid
  351. # Check if this device is really connected
  352. if not xs.read('', '/local/domain/%d/device/vbd/%d/state' % (vm_xid, block_name_to_devid(frontend))) == '4':
  353. # Do nothing - device already detached
  354. return
  355. xl_cmd = [ '/usr/sbin/xl', 'block-detach', str(vm_xid), str(frontend)]
  356. subprocess.check_call(xl_cmd)
  357. def block_detach_all(vm, vm_xid = None):
  358. """ Detach all non-system devices"""
  359. # Get XID if not provided already
  360. if vm_xid is None:
  361. if not vm.is_running():
  362. raise QubesException("VM %s not running" % vm.name)
  363. # FIXME: potential race
  364. vm_xid = vm.xid
  365. xs_trans = xs.transaction_start()
  366. devices = xs.ls(xs_trans, '/local/domain/%d/device/vbd' % vm_xid)
  367. if devices is None:
  368. return
  369. devices_to_detach = []
  370. for devid in devices:
  371. # check if this is system disk
  372. be_path = xs.read(xs_trans, '/local/domain/%d/device/vbd/%s/backend' % (vm_xid, devid))
  373. assert be_path is not None
  374. be_params = xs.read(xs_trans, be_path + '/params')
  375. if be_path.startswith('/local/domain/0/') and be_params is not None and be_params.startswith(system_path["qubes_base_dir"]):
  376. # system disk
  377. continue
  378. devices_to_detach.append(devid)
  379. xs.transaction_end(xs_trans)
  380. for devid in devices_to_detach:
  381. xl_cmd = [ '/usr/sbin/xl', 'block-detach', str(vm_xid), devid]
  382. subprocess.check_call(xl_cmd)
  383. ####### USB devices ######
  384. usb_ver_re = re.compile(r"^(1|2)$")
  385. usb_device_re = re.compile(r"^[0-9]+-[0-9]+(_[0-9]+)?$")
  386. usb_port_re = re.compile(r"^$|^[0-9]+-[0-9]+(\.[0-9]+)?$")
  387. def usb_setup(backend_vm_xid, vm_xid, devid, usb_ver):
  388. """
  389. Attach frontend to the backend.
  390. backend_vm_xid - id of the backend domain
  391. vm_xid - id of the frontend domain
  392. devid - id of the pvusb controller
  393. """
  394. num_ports = 8
  395. trans = xs.transaction_start()
  396. be_path = "/local/domain/%d/backend/vusb/%d/%d" % (backend_vm_xid, vm_xid, devid)
  397. fe_path = "/local/domain/%d/device/vusb/%d" % (vm_xid, devid)
  398. be_perm = [{'dom': backend_vm_xid}, {'dom': vm_xid, 'read': True} ]
  399. fe_perm = [{'dom': vm_xid}, {'dom': backend_vm_xid, 'read': True} ]
  400. # Create directories and set permissions
  401. xs.write(trans, be_path, "")
  402. xs.set_permissions(trans, be_path, be_perm)
  403. xs.write(trans, fe_path, "")
  404. xs.set_permissions(trans, fe_path, fe_perm)
  405. # Write backend information into the location that frontend looks for
  406. xs.write(trans, "%s/backend-id" % fe_path, str(backend_vm_xid))
  407. xs.write(trans, "%s/backend" % fe_path, be_path)
  408. # Write frontend information into the location that backend looks for
  409. xs.write(trans, "%s/frontend-id" % be_path, str(vm_xid))
  410. xs.write(trans, "%s/frontend" % be_path, fe_path)
  411. # Write USB Spec version field.
  412. xs.write(trans, "%s/usb-ver" % be_path, usb_ver)
  413. # Write virtual root hub field.
  414. xs.write(trans, "%s/num-ports" % be_path, str(num_ports))
  415. for port in range(1, num_ports+1):
  416. # Set all port to disconnected state
  417. xs.write(trans, "%s/port/%d" % (be_path, port), "")
  418. # Set state to XenbusStateInitialising
  419. xs.write(trans, "%s/state" % fe_path, "1")
  420. xs.write(trans, "%s/state" % be_path, "1")
  421. xs.write(trans, "%s/online" % be_path, "1")
  422. xs.transaction_end(trans)
  423. def usb_decode_device_from_xs(xs_encoded_device):
  424. """ recover actual device name (xenstore doesn't allow dot in key names, so it was translated to underscore) """
  425. return xs_encoded_device.replace('_', '.')
  426. def usb_encode_device_for_xs(device):
  427. """ encode actual device name (xenstore doesn't allow dot in key names, so translated it into underscore) """
  428. return device.replace('.', '_')
  429. def usb_list():
  430. """
  431. Returns a dictionary of USB devices (for PVUSB backends running in all VM).
  432. The dictionary is keyed by 'name' (see below), each element is a dictionary itself:
  433. vm = name of the backend domain
  434. xid = xid of the backend domain
  435. device = <frontend device number>-<frontend port number>
  436. name = <name of backend domain>:<frontend device number>-<frontend port number>
  437. desc = description
  438. """
  439. # FIXME: any better idea of desc_re?
  440. desc_re = re.compile(r"^.{1,255}$")
  441. devices_list = {}
  442. xs_trans = xs.transaction_start()
  443. vm_list = xs.ls(xs_trans, '/local/domain')
  444. for xid in vm_list:
  445. vm_name = xs.read(xs_trans, '/local/domain/%s/name' % xid)
  446. vm_devices = xs.ls(xs_trans, '/local/domain/%s/qubes-usb-devices' % xid)
  447. if vm_devices is None:
  448. continue
  449. # when listing devices in xenstore we get encoded names
  450. for xs_encoded_device in vm_devices:
  451. # Sanitize device id
  452. if not usb_device_re.match(xs_encoded_device):
  453. print >> sys.stderr, "Invalid device id in backend VM '%s'" % vm_name
  454. continue
  455. device = usb_decode_device_from_xs(xs_encoded_device)
  456. device_desc = xs.read(xs_trans, '/local/domain/%s/qubes-usb-devices/%s/desc' % (xid, xs_encoded_device))
  457. if not desc_re.match(device_desc):
  458. print >> sys.stderr, "Invalid %s device desc in VM '%s'" % (device, vm_name)
  459. continue
  460. visible_name = "%s:%s" % (vm_name, device)
  461. # grab version
  462. usb_ver = xs.read(xs_trans, '/local/domain/%s/qubes-usb-devices/%s/usb-ver' % (xid, xs_encoded_device))
  463. if usb_ver is None or not usb_ver_re.match(usb_ver):
  464. print >> sys.stderr, "Invalid %s device USB version in VM '%s'" % (device, vm_name)
  465. continue
  466. devices_list[visible_name] = {"name": visible_name, "xid":int(xid),
  467. "vm": vm_name, "device":device,
  468. "desc":device_desc,
  469. "usb_ver":usb_ver}
  470. xs.transaction_end(xs_trans)
  471. return devices_list
  472. def usb_check_attached(xs_trans, backend_vm, device):
  473. """
  474. Checks if the given device in the given backend attached to any frontend.
  475. Parameters:
  476. backend_vm - xid of the backend domain
  477. device - device name in the backend domain
  478. Returns None or a dictionary:
  479. vm - the name of the frontend domain
  480. xid - xid of the frontend domain
  481. frontend - frontend device number FIXME
  482. devid - frontend port number FIXME
  483. """
  484. # sample xs content: /local/domain/0/backend/vusb/4/0/port/1 = "7-5"
  485. attached_dev = None
  486. vms = xs.ls(xs_trans, '/local/domain/%d/backend/vusb' % backend_vm)
  487. if vms is None:
  488. return None
  489. for vm in vms:
  490. if not vm.isdigit():
  491. print >> sys.stderr, "Invalid VM id"
  492. continue
  493. frontend_devs = xs.ls(xs_trans, '/local/domain/%d/backend/vusb/%s' % (backend_vm, vm))
  494. if frontend_devs is None:
  495. continue
  496. for frontend_dev in frontend_devs:
  497. if not frontend_dev.isdigit():
  498. print >> sys.stderr, "Invalid frontend in VM %s" % vm
  499. continue
  500. ports = xs.ls(xs_trans, '/local/domain/%d/backend/vusb/%s/%s/port' % (backend_vm, vm, frontend_dev))
  501. if ports is None:
  502. continue
  503. for port in ports:
  504. # FIXME: refactor, see similar loop in usb_find_unused_frontend(), use usb_list() instead?
  505. if not port.isdigit():
  506. print >> sys.stderr, "Invalid port in VM %s frontend %s" % (vm, frontend)
  507. continue
  508. dev = xs.read(xs_trans, '/local/domain/%d/backend/vusb/%s/%s/port/%s' % (backend_vm, vm, frontend_dev, port))
  509. if dev == "":
  510. continue
  511. # Sanitize device id
  512. if not usb_port_re.match(dev):
  513. print >> sys.stderr, "Invalid device id in backend VM %d @ %s/%s/port/%s" % \
  514. (backend_vm, vm, frontend_dev, port)
  515. continue
  516. if dev == device:
  517. frontend = "%s-%s" % (frontend_dev, port)
  518. vm_name = xl_ctx.domid_to_name(int(vm))
  519. if vm_name is None:
  520. # FIXME: should we wipe references to frontends running on nonexistent VMs?
  521. continue
  522. attached_dev = {"xid":int(vm), "frontend": frontend, "devid": device, "vm": vm_name}
  523. break
  524. return attached_dev
  525. #def usb_check_frontend_busy(vm, front_dev, port):
  526. # devport = frontend.split("-")
  527. # if len(devport) != 2:
  528. # raise QubesException("Malformed frontend syntax, must be in device-port format")
  529. # # FIXME:
  530. # # return xs.read('', '/local/domain/%d/device/vusb/%d/state' % (vm.xid, frontend)) == '4'
  531. # return False
  532. def usb_find_unused_frontend(xs_trans, backend_vm_xid, vm_xid, usb_ver):
  533. """
  534. Find an unused frontend/port to link the given backend with the given frontend.
  535. Creates new frontend if needed.
  536. Returns frontend specification in <device>-<port> format.
  537. """
  538. # This variable holds an index of last frontend scanned by the loop below.
  539. # If nothing found, this value will be used to derive the index of a new frontend.
  540. last_frontend_dev = -1
  541. frontend_devs = xs.ls(xs_trans, "/local/domain/%d/device/vusb" % vm_xid)
  542. if frontend_devs is not None:
  543. for frontend_dev in frontend_devs:
  544. if not frontend_dev.isdigit():
  545. print >> sys.stderr, "Invalid frontend_dev in VM %d" % vm_xid
  546. continue
  547. frontend_dev = int(frontend_dev)
  548. fe_path = "/local/domain/%d/device/vusb/%d" % (vm_xid, frontend_dev)
  549. if xs.read(xs_trans, "%s/backend-id" % fe_path) == str(backend_vm_xid):
  550. if xs.read(xs_trans, '/local/domain/%d/backend/vusb/%d/%d/usb-ver' % (backend_vm_xid, vm_xid, frontend_dev)) != usb_ver:
  551. last_frontend_dev = frontend_dev
  552. continue
  553. # here: found an existing frontend already connected to right backend using an appropriate USB version
  554. ports = xs.ls(xs_trans, '/local/domain/%d/backend/vusb/%d/%d/port' % (backend_vm_xid, vm_xid, frontend_dev))
  555. if ports is None:
  556. print >> sys.stderr, "No ports in VM %d frontend_dev %d?" % (vm_xid, frontend_dev)
  557. last_frontend_dev = frontend_dev
  558. continue
  559. for port in ports:
  560. # FIXME: refactor, see similar loop in usb_check_attached(), use usb_list() instead?
  561. if not port.isdigit():
  562. print >> sys.stderr, "Invalid port in VM %d frontend_dev %d" % (vm_xid, frontend_dev)
  563. continue
  564. port = int(port)
  565. dev = xs.read(xs_trans, '/local/domain/%d/backend/vusb/%d/%s/port/%s' % (backend_vm_xid, vm_xid, frontend_dev, port))
  566. # Sanitize device id
  567. if not usb_port_re.match(dev):
  568. print >> sys.stderr, "Invalid device id in backend VM %d @ %d/%d/port/%d" % \
  569. (backend_vm_xid, vm_xid, frontend_dev, port)
  570. continue
  571. if dev == "":
  572. return '%d-%d' % (frontend_dev, port)
  573. last_frontend_dev = frontend_dev
  574. # create a new frontend_dev and link it to the backend
  575. frontend_dev = last_frontend_dev + 1
  576. usb_setup(backend_vm_xid, vm_xid, frontend_dev, usb_ver)
  577. return '%d-%d' % (frontend_dev, 1)
  578. def usb_attach(vm, backend_vm, device, frontend=None, auto_detach=False, wait=True):
  579. device_attach_check(vm, backend_vm, device, frontend)
  580. xs_trans = xs.transaction_start()
  581. xs_encoded_device = usb_encode_device_for_xs(device)
  582. usb_ver = xs.read(xs_trans, '/local/domain/%s/qubes-usb-devices/%s/usb-ver' % (backend_vm.xid, xs_encoded_device))
  583. if usb_ver is None or not usb_ver_re.match(usb_ver):
  584. xs.transaction_end(xs_trans)
  585. raise QubesException("Invalid %s device USB version in VM '%s'" % (device, backend_vm.name))
  586. if frontend is None:
  587. frontend = usb_find_unused_frontend(xs_trans, backend_vm.xid, vm.xid, usb_ver)
  588. else:
  589. # Check if any device attached at this frontend
  590. #if usb_check_frontend_busy(vm, frontend):
  591. # raise QubesException("Frontend %s busy in VM %s, detach it first" % (frontend, vm.name))
  592. xs.transaction_end(xs_trans)
  593. raise NotImplementedError("Explicit USB frontend specification is not implemented yet")
  594. # Check if this device is attached to some domain
  595. attached_vm = usb_check_attached(xs_trans, backend_vm.xid, device)
  596. xs.transaction_end(xs_trans)
  597. if attached_vm:
  598. if auto_detach:
  599. usb_detach(backend_vm, attached_vm)
  600. else:
  601. raise QubesException("Device %s from %s already connected to VM %s as %s" % (device, backend_vm.name, attached_vm['vm'], attached_vm['frontend']))
  602. # Run helper script
  603. xl_cmd = [ '/usr/lib/qubes/xl-qvm-usb-attach.py', str(vm.xid), device, frontend, str(backend_vm.xid) ]
  604. subprocess.check_call(xl_cmd)
  605. def usb_detach(backend_vm, attachment):
  606. xl_cmd = [ '/usr/lib/qubes/xl-qvm-usb-detach.py', str(attachment['xid']), attachment['devid'], attachment['frontend'], str(backend_vm.xid) ]
  607. subprocess.check_call(xl_cmd)
  608. def usb_detach_all(vm):
  609. raise NotImplementedError("Detaching all devices from a given VM is not implemented yet")
  610. ####### QubesWatch ######
  611. def only_in_first_list(l1, l2):
  612. ret=[]
  613. for i in l1:
  614. if not i in l2:
  615. ret.append(i)
  616. return ret
  617. class QubesWatch(object):
  618. class WatchType(object):
  619. def __init__(self, fn, param):
  620. self.fn = fn
  621. self.param = param
  622. def __init__(self):
  623. self.xs = xen.lowlevel.xs.xs()
  624. self.watch_tokens_block = {}
  625. self.watch_tokens_vbd = {}
  626. self.watch_tokens_meminfo = {}
  627. self.block_callback = None
  628. self.meminfo_callback = None
  629. self.domain_callback = None
  630. self.xs.watch('@introduceDomain', QubesWatch.WatchType(self.domain_list_changed, None))
  631. self.xs.watch('@releaseDomain', QubesWatch.WatchType(self.domain_list_changed, None))
  632. def setup_block_watch(self, callback):
  633. old_block_callback = self.block_callback
  634. self.block_callback = callback
  635. if old_block_callback is not None and callback is None:
  636. # remove watches
  637. self.update_watches_block([])
  638. else:
  639. # possibly add watches
  640. self.domain_list_changed(None)
  641. def setup_meminfo_watch(self, callback):
  642. old_meminfo_callback = self.meminfo_callback
  643. self.meminfo_callback = callback
  644. if old_meminfo_callback is not None and callback is None:
  645. # remove watches
  646. self.update_watches_meminfo([])
  647. else:
  648. # possibly add watches
  649. self.domain_list_changed(None)
  650. def setup_domain_watch(self, callback):
  651. self.domain_callback = callback
  652. def get_block_key(self, xid):
  653. return '/local/domain/%s/qubes-block-devices' % xid
  654. def get_vbd_key(self, xid):
  655. return '/local/domain/%s/device/vbd' % xid
  656. def get_meminfo_key(self, xid):
  657. return '/local/domain/%s/memory/meminfo' % xid
  658. def update_watches(self, xid_list, watch_tokens, xs_key_func, callback):
  659. for i in only_in_first_list(xid_list, watch_tokens.keys()):
  660. #new domain has been created
  661. watch = QubesWatch.WatchType(callback, i)
  662. watch_tokens[i] = watch
  663. self.xs.watch(xs_key_func(i), watch)
  664. for i in only_in_first_list(watch_tokens.keys(), xid_list):
  665. #domain destroyed
  666. self.xs.unwatch(xs_key_func(i), watch_tokens[i])
  667. watch_tokens.pop(i)
  668. def update_watches_block(self, xid_list):
  669. self.update_watches(xid_list, self.watch_tokens_block,
  670. self.get_block_key, self.block_callback)
  671. self.update_watches(xid_list, self.watch_tokens_vbd,
  672. self.get_vbd_key, self.block_callback)
  673. def update_watches_meminfo(self, xid_list):
  674. self.update_watches(xid_list, self.watch_tokens_meminfo,
  675. self.get_meminfo_key, self.meminfo_callback)
  676. def domain_list_changed(self, param):
  677. curr = self.xs.ls('', '/local/domain')
  678. if curr == None:
  679. return
  680. if self.domain_callback:
  681. self.domain_callback()
  682. if self.block_callback:
  683. self.update_watches_block(curr)
  684. if self.meminfo_callback:
  685. self.update_watches_meminfo(curr)
  686. def watch_single(self):
  687. result = self.xs.read_watch()
  688. token = result[1]
  689. token.fn(token.param)
  690. def watch_loop(self):
  691. while True:
  692. self.watch_single()
  693. ##### updates check #####
  694. UPDATES_DOM0_DISABLE_FLAG='/var/lib/qubes/updates/disable-updates'
  695. def updates_vms_toggle(qvm_collection, value):
  696. for vm in qvm_collection.values():
  697. if vm.qid == 0:
  698. continue
  699. if value:
  700. vm.services.pop('qubes-update-check', None)
  701. if vm.is_running():
  702. try:
  703. vm.run("systemctl start qubes-update-check.timer",
  704. user="root")
  705. except:
  706. pass
  707. else:
  708. vm.services['qubes-update-check'] = False
  709. if vm.is_running():
  710. try:
  711. vm.run("systemctl stop qubes-update-check.timer",
  712. user="root")
  713. except:
  714. pass
  715. def updates_dom0_toggle(qvm_collection, value):
  716. if value:
  717. if os.path.exists(UPDATES_DOM0_DISABLE_FLAG):
  718. os.unlink(UPDATES_DOM0_DISABLE_FLAG)
  719. else:
  720. open(UPDATES_DOM0_DISABLE_FLAG, "w").close()
  721. def updates_dom0_status(qvm_collection):
  722. return not os.path.exists(UPDATES_DOM0_DISABLE_FLAG)
  723. # vim:sw=4:et: