qvm_device.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. # pylint: disable=C,R
  2. #
  3. # The Qubes OS Project, http://www.qubes-os.org
  4. #
  5. # Copyright (C) 2016 Bahtiar `kalkin-` Gadimov <bahtiar@gadimov.de>
  6. # Copyright (C) 2016 Marek Marczykowski-Górecki
  7. # <marmarek@invisiblethingslab.com>
  8. #
  9. # This program is free software; you can redistribute it and/or modify
  10. # it under the terms of the GNU General Public License as published by
  11. # the Free Software Foundation; either version 2 of the License, or
  12. # (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 along
  20. # with this program; if not, write to the Free Software Foundation, Inc.,
  21. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  22. '''Qubes volume and block device managment'''
  23. from __future__ import print_function
  24. import argparse
  25. import os
  26. import sys
  27. import qubes
  28. import qubes.devices
  29. import qubes.exc
  30. import qubes.tools
  31. def prepare_table(dev_list):
  32. ''' Converts a list of :py:class:`qubes.devices.DeviceInfo` objects to a
  33. list of tupples for the :py:func:`qubes.tools.print_table`.
  34. If :program:`qvm-devices` is running in a TTY, it will ommit duplicate
  35. data.
  36. :param list dev_list: List of :py:class:`qubes.devices.DeviceInfo`
  37. objects.
  38. :returns: list of tupples
  39. '''
  40. output = []
  41. header = []
  42. if sys.stdout.isatty():
  43. header += [('BACKEND:DEVID', 'DESCRIPTION', 'USED BY')] # NOQA
  44. for dev in dev_list:
  45. output += [(
  46. "{!s}:{!s}".format(dev.backend_domain, dev.ident),
  47. dev.description,
  48. str(dev.frontend_domain) if dev.frontend_domain else "",
  49. )]
  50. return header + sorted(output)
  51. def list_devices(args):
  52. ''' Called by the parser to execute the qubes-devices list
  53. subcommand. '''
  54. app = args.app
  55. result = []
  56. if hasattr(args, 'domains') and args.domains:
  57. for domain in args.domains:
  58. result.extend(domain.devices[args.devclass].attached())
  59. else:
  60. for backend in app.domains:
  61. result.extend(backend.devices[args.devclass])
  62. qubes.tools.print_table(prepare_table(result))
  63. def attach_device(args):
  64. ''' Called by the parser to execute the :program:`qvm-devices attach`
  65. subcommand.
  66. '''
  67. device = args.device
  68. vm = args.domains[0]
  69. vm.devices[args.devclass].attach(device)
  70. def detach_device(args):
  71. ''' Called by the parser to execute the :program:`qvm-devices detach`
  72. subcommand.
  73. '''
  74. device = args.device
  75. vm = args.domains[0]
  76. vm.devices[args.devclass].detach(device)
  77. def init_list_parser(sub_parsers):
  78. ''' Configures the parser for the :program:`qvm-devices list` subcommand '''
  79. # pylint: disable=protected-access
  80. list_parser = sub_parsers.add_parser('list', aliases=('ls', 'l'),
  81. help='list devices')
  82. vm_name_group = qubes.tools.VmNameGroup(
  83. list_parser, required=False, vm_action=qubes.tools.VmNameAction,
  84. help='list devices assigned to specific domain(s)')
  85. list_parser._mutually_exclusive_groups.append(vm_name_group)
  86. list_parser.set_defaults(func=list_devices)
  87. class DeviceAction(qubes.tools.QubesAction):
  88. ''' Action for argument parser that gets the
  89. :py:class:``qubes.storage.Volume`` from a POOL_NAME:VOLUME_ID string.
  90. '''
  91. # pylint: disable=too-few-public-methods
  92. def __init__(self, help='A domain & device id combination',
  93. required=True, allow_unknown=False, **kwargs):
  94. # pylint: disable=redefined-builtin
  95. super(DeviceAction, self).__init__(help=help, required=required,
  96. **kwargs)
  97. self.allow_unknown = allow_unknown
  98. def __call__(self, parser, namespace, values, option_string=None):
  99. ''' Set ``namespace.device`` to ``values`` '''
  100. setattr(namespace, self.dest, values)
  101. def parse_qubes_app(self, parser, namespace):
  102. ''' Acquire the :py:class:``qubes.devices.DeviceInfo`` object from
  103. ``namespace.app``.
  104. '''
  105. assert hasattr(namespace, 'app')
  106. assert hasattr(namespace, 'devclass')
  107. app = namespace.app
  108. devclass = namespace.devclass
  109. try:
  110. backend_name, devid = getattr(namespace, self.dest).split(':', 1)
  111. try:
  112. backend = app.domains[backend_name]
  113. dev = backend.devices[devclass][devid]
  114. if not self.allow_unknown and isinstance(dev,
  115. qubes.devices.UnknownDevice):
  116. parser.error_runtime('no device {!r} in qube {!r}'.format(
  117. backend_name, devid))
  118. except KeyError:
  119. parser.error_runtime('no domain {!r}'.format(backend_name))
  120. except ValueError:
  121. parser.error('expected a domain & device id combination like '
  122. 'foo:bar')
  123. def get_parser(device_class=None):
  124. '''Create :py:class:`argparse.ArgumentParser` suitable for
  125. :program:`qvm-block`.
  126. '''
  127. parser = qubes.tools.QubesArgumentParser(description=__doc__, want_app=True)
  128. parser.register('action', 'parsers', qubes.tools.AliasedSubParsersAction)
  129. if device_class:
  130. parser.add_argument('devclass', const=device_class,
  131. action='store_const',
  132. help=argparse.SUPPRESS)
  133. else:
  134. parser.add_argument('devclass', metavar='DEVICE_CLASS', action='store',
  135. help="Device class to manage ('pci', 'usb', etc)")
  136. sub_parsers = parser.add_subparsers(
  137. title='commands',
  138. description="For more information see qvm-device command -h",
  139. dest='command')
  140. init_list_parser(sub_parsers)
  141. attach_parser = sub_parsers.add_parser(
  142. 'attach', help="Attach device to domain", aliases=('at', 'a'))
  143. attach_parser.add_argument('VMNAME', action=qubes.tools.RunningVmNameAction)
  144. attach_parser.add_argument(metavar='BACKEND:DEVICE_ID', dest='device',
  145. action=qubes.tools.VolumeAction)
  146. attach_parser.set_defaults(func=detach_device)
  147. detach_parser = sub_parsers.add_parser(
  148. "detach", help="Detach device from domain", aliases=('d', 'dt'))
  149. detach_parser.add_argument('VMNAME', action=qubes.tools.RunningVmNameAction)
  150. detach_parser.add_argument(metavar='BACKEND:DEVICE_ID', dest='device',
  151. action=qubes.tools.VolumeAction)
  152. detach_parser.set_defaults(func=detach_device)
  153. return parser
  154. def main(args=None):
  155. '''Main routine of :program:`qvm-block`.'''
  156. basename = os.path.basename(sys.argv[0])
  157. devclass = None
  158. if basename.startswith('qvm-') and basename != 'qvm-device':
  159. devclass = basename[4:]
  160. args = get_parser(devclass).parse_args(args)
  161. try:
  162. args.func(args)
  163. except qubes.exc.QubesException as e:
  164. print(str(e), file=sys.stderr)
  165. return 1
  166. return 0
  167. if __name__ == '__main__':
  168. sys.exit(main())