qubesutils.py 32 KB

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