qvm_volume.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. # encoding=utf-8
  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) 2017 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 Lesser General Public License as published by
  11. # the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
  18. #
  19. # You should have received a copy of the GNU Lesser General Public License along
  20. # with this program; if not, see <http://www.gnu.org/licenses/>.
  21. '''Qubes volume management'''
  22. from __future__ import print_function
  23. import sys
  24. import qubesadmin
  25. import qubesadmin.exc
  26. import qubesadmin.tools
  27. import qubesadmin.utils
  28. def prepare_table(vd_list, full=False):
  29. ''' Converts a list of :py:class:`VolumeData` objects to a list of tupples
  30. for the :py:func:`qubes.tools.print_table`.
  31. If :program:`qvm-volume` is running in a TTY, it will ommit duplicate
  32. data.
  33. :param list vd_list: List of :py:class:`VolumeData` objects.
  34. :param bool full: If set to true duplicate data is printed even when
  35. running from TTY.
  36. :returns: list of tupples
  37. '''
  38. output = []
  39. output += [('POOL:VOLUME', 'VMNAME', 'VOLUME_NAME', 'REVERT_POSSIBLE')]
  40. for volume in sorted(vd_list):
  41. if volume.domains:
  42. vmname, volume_name = volume.domains.pop()
  43. output += [(str(volume), vmname, volume_name, volume.revisions)]
  44. for tupple in volume.domains:
  45. vmname, volume_name = tupple
  46. if full or not sys.stdout.isatty():
  47. output += [(str(volume), vmname, volume_name,
  48. volume.revisions)]
  49. else:
  50. output += [('', vmname, volume_name, volume.revisions)]
  51. else:
  52. output += [(str(volume), "")]
  53. return output
  54. class VolumeData(object):
  55. ''' Wrapper object around :py:class:`qubes.storage.Volume`, mainly to track
  56. the domains a volume is attached to.
  57. '''
  58. # pylint: disable=too-few-public-methods
  59. def __init__(self, volume):
  60. self.pool = volume.pool
  61. self.vid = volume.vid
  62. if volume.revisions:
  63. self.revisions = 'Yes'
  64. else:
  65. self.revisions = 'No'
  66. self.domains = []
  67. def __lt__(self, other):
  68. return (self.pool, self.vid) < (other.pool, other.vid)
  69. def __str__(self):
  70. return "{!s}:{!s}".format(self.pool, self.vid)
  71. def list_volumes(args):
  72. ''' Called by the parser to execute the qvm-volume list subcommand. '''
  73. app = args.app
  74. if hasattr(args, 'domains') and args.domains:
  75. domains = args.domains
  76. else:
  77. domains = app.domains
  78. volumes = [v for vm in domains for v in vm.volumes.values()]
  79. if args.pools:
  80. # only specified pools
  81. volumes = [v for v in volumes if v.pool in args.pools]
  82. if not args.internal: # hide internal volumes
  83. volumes = [v for v in volumes if not v.internal]
  84. vd_dict = {}
  85. for volume in volumes:
  86. volume_data = VolumeData(volume)
  87. try:
  88. vd_dict[volume.pool][volume.vid] = volume_data
  89. except KeyError:
  90. vd_dict[volume.pool] = {volume.vid: volume_data}
  91. for domain in domains: # gather the domain names
  92. try:
  93. for name, volume in domain.volumes.items():
  94. try:
  95. volume_data = vd_dict[volume.pool][volume.vid]
  96. volume_data.domains += [(domain.name, name)]
  97. except KeyError:
  98. # Skipping volume
  99. continue
  100. except AttributeError:
  101. # Skipping domain without volumes
  102. continue
  103. if hasattr(args, 'domains') and args.domains:
  104. result = [x # reduce to only VolumeData with assigned domains
  105. for p in vd_dict.values() for x in p.values()
  106. if x.domains]
  107. else:
  108. result = [x for p in vd_dict.values() for x in p.values()]
  109. qubesadmin.tools.print_table(prepare_table(result, full=args.full))
  110. def revert_volume(args):
  111. ''' Revert volume to previous state '''
  112. volume = args.volume
  113. app = args.app
  114. try:
  115. pool = app.pools[volume.pool]
  116. pool.revert(volume)
  117. except qubesadmin.exc.StoragePoolException as e:
  118. print(str(e), file=sys.stderr)
  119. sys.exit(1)
  120. def extend_volumes(args):
  121. ''' Called by the parser to execute the :program:`qvm-block extend`
  122. subcommand
  123. '''
  124. volume = args.volume
  125. size = qubesadmin.utils.parse_size(args.size)
  126. volume.resize(size)
  127. def init_list_parser(sub_parsers):
  128. ''' Configures the parser for the :program:`qvm-block list` subcommand '''
  129. # pylint: disable=protected-access
  130. list_parser = sub_parsers.add_parser('list', aliases=('ls', 'l'),
  131. help='list storage volumes')
  132. list_parser.add_argument('-p', '--pool', dest='pools',
  133. action=qubesadmin.tools.PoolsAction)
  134. list_parser.add_argument('-i', '--internal', action='store_true',
  135. help='Show internal volumes')
  136. list_parser.add_argument(
  137. '--full', action='store_true',
  138. help='print full line for each POOL_NAME:VOLUME_ID & vm combination')
  139. vm_name_group = qubesadmin.tools.VmNameGroup(
  140. list_parser, required=False, vm_action=qubesadmin.tools.VmNameAction,
  141. help='list volumes from specified domain(s)')
  142. list_parser._mutually_exclusive_groups.append(vm_name_group)
  143. list_parser.set_defaults(func=list_volumes)
  144. def init_revert_parser(sub_parsers):
  145. ''' Add 'revert' action related options '''
  146. revert_parser = sub_parsers.add_parser(
  147. 'revert', aliases=('rv', 'r'),
  148. help='revert volume to previous revision')
  149. revert_parser.add_argument(metavar='VM:VOLUME', dest='volume',
  150. action=qubesadmin.tools.VMVolumeAction)
  151. revert_parser.set_defaults(func=revert_volume)
  152. def init_extend_parser(sub_parsers):
  153. ''' Add 'extend' action related options '''
  154. extend_parser = sub_parsers.add_parser(
  155. "extend", help="extend volume from domain")
  156. extend_parser.add_argument(metavar='VM:VOLUME', dest='volume',
  157. action=qubesadmin.tools.VMVolumeAction)
  158. extend_parser.add_argument('size', help='New size in bytes')
  159. extend_parser.set_defaults(func=extend_volumes)
  160. def get_parser():
  161. '''Create :py:class:`argparse.ArgumentParser` suitable for
  162. :program:`qvm-block`.
  163. '''
  164. parser = qubesadmin.tools.QubesArgumentParser(description=__doc__,
  165. want_app=True)
  166. parser.register('action', 'parsers',
  167. qubesadmin.tools.AliasedSubParsersAction)
  168. sub_parsers = parser.add_subparsers(
  169. title='commands',
  170. description="For more information see qvm-block command -h",
  171. dest='command')
  172. init_extend_parser(sub_parsers)
  173. init_list_parser(sub_parsers)
  174. init_revert_parser(sub_parsers)
  175. return parser
  176. def main(args=None, app=None):
  177. '''Main routine of :program:`qvm-block`.'''
  178. parser = get_parser()
  179. try:
  180. args = parser.parse_args(args, app=app)
  181. args.func(args)
  182. except qubesadmin.exc.QubesException as e:
  183. parser.print_error(str(e))
  184. return 1
  185. return 0
  186. if __name__ == '__main__':
  187. sys.exit(main())