qubes_lvm.py 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. #!/usr/bin/python2
  2. #
  3. # The Qubes OS Project, http://www.qubes-os.org
  4. #
  5. # Copyright (C) 2016 Bahtiar `kalkin-` Gadimov <bahtiar@gadimov.de>
  6. #
  7. # This program is free software; you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation; either version 2 of the License, or
  10. # (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 along
  18. # with this program; if not, write to the Free Software Foundation, Inc.,
  19. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  20. #
  21. ''' Manage pools and volumes managed by the 'lvm_thin' driver. '''
  22. from __future__ import print_function
  23. import argparse
  24. import logging
  25. import subprocess
  26. import sys
  27. import time
  28. import lvm # pylint: disable=import-error
  29. log = logging.getLogger('qubes.storage.lvm')
  30. def pool_exists(args):
  31. """ Check if given name is an lvm thin volume. """
  32. # TODO Implement a faster and proper working version pool_exists
  33. vg_name, thin_pool_name = args.pool_id.split('/', 1)
  34. volume_group = lvm.vgOpen(vg_name)
  35. for p in volume_group.listLVs():
  36. if p.getAttr()[0] == 't' and p.getName() == thin_pool_name:
  37. volume_group.close()
  38. return True
  39. volume_group.close()
  40. return False
  41. def volume_exists(volume):
  42. """ Check if the given volume exists and is a thin volume """
  43. log.debug("Checking if the %s thin volume exists", volume)
  44. assert volume is not None
  45. vg_name, volume_name = volume.split('/', 1)
  46. volume_group = lvm.vgOpen(vg_name)
  47. for p in volume_group.listLVs():
  48. if p.getAttr()[0] == 'V' and p.getName() == volume_name:
  49. volume_group.close()
  50. return True
  51. volume_group.close()
  52. return False
  53. def remove_volume(args):
  54. """ Tries to remove the specified logical volume.
  55. If the removal fails it will try up to 3 times waiting 1, 2 and 3
  56. seconds between tries. Most of the time this function fails if some
  57. process still has the volume locked.
  58. """
  59. img = args.name
  60. if not volume_exists(img):
  61. log.info("Expected to remove %s, but volume does not exist", img)
  62. return
  63. tries = 1
  64. successful = False
  65. cmd = ['lvremove', '-f', img]
  66. while tries <= 3 and not successful:
  67. log.info("Trying to remove LVM %s", img)
  68. try:
  69. output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
  70. log.debug(output)
  71. successful = True
  72. except subprocess.CalledProcessError:
  73. successful = False
  74. if successful:
  75. break
  76. else:
  77. time.sleep(tries)
  78. tries += 1
  79. if not successful:
  80. log.error('Could not remove volume ' + img)
  81. def clone_volume(args):
  82. """ Calls lvcreate and creates new snapshot. """
  83. old = args.source
  84. new_name = args.destination
  85. cmd = ["lvcreate", "-kn", "-ay", "-s", old, "-n", new_name]
  86. return subprocess.call(cmd)
  87. def new_volume(args):
  88. ''' Creates a new volume in the specified thin pool, formated with ext4 '''
  89. thin_pool = args.pool_id
  90. name = args.name
  91. size = args.size
  92. log.info('Creating new Thin LVM %s in %s VG %s bytes', name, thin_pool,
  93. size)
  94. cmd = ['lvcreate', '-T', thin_pool, '-kn', '-ay', '-n', name, '-V',
  95. str(size) + 'B']
  96. return subprocess.call(cmd)
  97. def rename_volume(old_name, new_name):
  98. ''' Rename volume '''
  99. log.debug("Renaming LVM %s to %s ", old_name, new_name)
  100. retcode = subprocess.call(["lvrename", old_name, new_name])
  101. if retcode != 0:
  102. raise IOError("Error renaming LVM %s to %s " % (old_name, new_name))
  103. return new_name
  104. def extend_volume(args):
  105. ''' Extends an existing lvm volume. Note this works on any lvm volume not
  106. only on thin volumes.
  107. '''
  108. vid = args.name
  109. size = int(args.size) / (1000 * 1000)
  110. log.debug("Extending LVM %s to %s", vid, size)
  111. cmd = ["lvextend", "-L%s" % size, vid]
  112. log.debug(cmd)
  113. retcode = subprocess.call(cmd)
  114. if retcode != 0:
  115. raise IOError("Error extending LVM %s to %s " % (vid, size))
  116. return 0
  117. def init_pool_parser(sub_parsers):
  118. ''' Initialize pool subparser '''
  119. pool_parser = sub_parsers.add_parser(
  120. 'pool',
  121. help="Exit with exit code 0 if pool exists")
  122. pool_parser.add_argument('pool_id', metavar='VG/POOL',
  123. help="volume_group/pool_name")
  124. pool_parser.set_defaults(func=pool_exists)
  125. def init_new_parser(sub_parsers):
  126. ''' Initialize the 'new' subparser '''
  127. new_parser = sub_parsers.add_parser(
  128. 'create',
  129. help='Creates a new thin ThinPoolLogicalVolume')
  130. new_parser.add_argument('pool_id', metavar='VG/POOL',
  131. help="volume_group/pool_name")
  132. new_parser.add_argument('name',
  133. help='name of the new ThinPoolLogicalVolume')
  134. new_parser.add_argument(
  135. 'size', help='size in bytes of the new ThinPoolLogicalVolume')
  136. new_parser.set_defaults(func=new_volume)
  137. def init_import_parser(sub_parsers):
  138. ''' Initialize import subparser '''
  139. import_parser = sub_parsers.add_parser(
  140. 'import',
  141. help='sparse copy data from stdin to a thin volume')
  142. import_parser.add_argument('name', metavar='VG/VID',
  143. help='volume_group/volume_name')
  144. import_parser.set_defaults(func=import_volume)
  145. def init_clone_parser(sub_parsers):
  146. ''' Initialize clone subparser '''
  147. clone_parser = sub_parsers.add_parser(
  148. 'clone',
  149. help='sparse copy data from stdin to a thin volume')
  150. clone_parser.add_argument('source', metavar='VG/VID',
  151. help='volume_group/volume_name')
  152. clone_parser.add_argument('destination', metavar='VG/VID',
  153. help='volume_group/volume_name')
  154. clone_parser.set_defaults(func=clone_volume)
  155. def import_volume(args):
  156. ''' Imports from stdin to a thin volume '''
  157. name = args.name
  158. src = sys.stdin
  159. blk_size = 4096
  160. zeros = '\x00' * blk_size
  161. dst_path = '/dev/%s' % name
  162. with open(dst_path, 'wb') as dst:
  163. while True:
  164. tmp = src.read(blk_size)
  165. if not tmp:
  166. break
  167. elif tmp == zeros:
  168. dst.seek(blk_size, 1)
  169. else:
  170. dst.write(tmp)
  171. def list_volumes(args):
  172. ''' lists volumes '''
  173. vg_name, _ = args.name.split('/')
  174. volume_group = lvm.vgOpen(vg_name)
  175. for p in volume_group.listLVs():
  176. if p.getAttr()[0] == 'V':
  177. print(vg_name + "/" + p.getName() + ' ' + p.getAttr())
  178. volume_group.close()
  179. def init_volumes_parser(sub_parsers):
  180. ''' Initialize volumes subparser '''
  181. parser = sub_parsers.add_parser('volumes',
  182. help='list volumes in a pool')
  183. parser.add_argument('name', metavar='VG/THIN_POOL',
  184. help='volume_group/thin_pool_name')
  185. parser.set_defaults(func=list_volumes)
  186. def init_remove_parser(sub_parsers):
  187. ''' Initialize remove subparser '''
  188. remove_parser = sub_parsers.add_parser('remove',
  189. help='Removes a LogicalVolume')
  190. remove_parser.add_argument('name', metavar='VG/VID',
  191. help='volume_group/volume_name')
  192. remove_parser.set_defaults(func=remove_volume)
  193. def init_extend_parser(sub_parsers):
  194. ''' Initialize extend subparser '''
  195. extend_parser = sub_parsers.add_parser('extend',
  196. help='extends a LogicalVolume')
  197. extend_parser.add_argument('name', metavar='VG/VID',
  198. help='volume_group/volume_name')
  199. extend_parser.set_defaults(func=extend_volume)
  200. extend_parser.add_argument(
  201. 'size', help='size in bytes of the new ThinPoolLogicalVolume')
  202. def get_parser():
  203. '''Create :py:class:`argparse.ArgumentParser` suitable for
  204. :program:`qubes-lvm`.
  205. '''
  206. parser = argparse.ArgumentParser(description=__doc__)
  207. # pylint: disable=protected-access
  208. parser.register('action', 'parsers', argparse._SubParsersAction)
  209. sub_parsers = parser.add_subparsers(
  210. title='commands',
  211. description="For more information see qubes-lvm command -h",
  212. dest='command')
  213. init_clone_parser(sub_parsers)
  214. init_extend_parser(sub_parsers)
  215. init_import_parser(sub_parsers)
  216. init_new_parser(sub_parsers)
  217. init_pool_parser(sub_parsers)
  218. init_remove_parser(sub_parsers)
  219. init_volumes_parser(sub_parsers)
  220. return parser
  221. def main(args=None):
  222. '''Main routine of :program:`qubes-lvm`.'''
  223. args = get_parser().parse_args(args)
  224. return args.func(args)
  225. if __name__ == '__main__':
  226. sys.exit(main())