qubesutils.py 31 KB

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