diff --git a/qubes/storage/lvm.py b/qubes/storage/lvm.py index 4d59ca47..fd0cbdc2 100644 --- a/qubes/storage/lvm.py +++ b/qubes/storage/lvm.py @@ -267,37 +267,27 @@ class ThinPool(qubes.storage.Pool): def verify(self, volume): ''' Verifies the volume. ''' - cmd = ['sudo', 'qubes-lvm', 'volumes', - self.volume_group + '/' + self.thin_pool] - p = subprocess.Popen(cmd, stdout=subprocess.PIPE) - result = p.communicate()[0] - for line in result.splitlines(): - if not line.strip(): - continue - vid, atr = line.strip().split(' ') - if vid == volume.vid: - return atr[4] == 'a' - - return False + try: + vol_info = size_cache[volume.vid] + return vol_info['attr'][4] == 'a' + except KeyError: + return False @property def volumes(self): ''' Return a list of volumes managed by this pool ''' - cmd = ['sudo', 'qubes-lvm', 'volumes', - self.volume_group + '/' + self.thin_pool] - p = subprocess.Popen(cmd, stdout=subprocess.PIPE) - result = p.communicate()[0] volumes = [] - for line in result.splitlines(): - if not line.strip(): + for vid, vol_info in size_cache.items(): + if not vid.startswith(self.volume_group + '/'): + continue + if vol_info['pool_lv'] != self.thin_pool: continue - vid, atr = line.strip().split(' ') config = { 'pool': self.name, 'vid': vid, 'name': vid, 'volume_group': self.volume_group, - 'rw': atr[1] == 'w', + 'rw': vol_info['attr'][1] == 'w', } volumes += [ThinVolume(**config)] return volumes @@ -315,10 +305,13 @@ class ThinPool(qubes.storage.Pool): def init_cache(log=logging.getLogger('qube.storage.lvm')): - cmd = ['sudo', 'lvs', '--noheadings', '-o', - 'vg_name,name,lv_size,data_percent', '--units', 'b', '--separator', - ','] - p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + cmd = ['lvs', '--noheadings', '-o', + 'vg_name,pool_lv,name,lv_size,data_percent,lv_attr', + '--units', 'b', '--separator', ','] + if os.getuid() != 0: + cmd.insert(0, 'sudo') + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + close_fds=True) out, err = p.communicate() return_code = p.returncode if return_code == 0 and err: @@ -330,13 +323,14 @@ def init_cache(log=logging.getLogger('qube.storage.lvm')): for line in out.splitlines(): line = line.strip() - pool_name, name, size, usage_percent = line.split(',', 3) - if '' in [pool_name, name, size, usage_percent]: + pool_name, pool_lv, name, size, usage_percent, attr = line.split(',', 5) + if '' in [pool_name, pool_lv, name, size, usage_percent]: continue name = pool_name + "/" + name size = int(size[:-1]) usage = int(size / 100 * float(usage_percent)) - result[name] = {'size':size, 'usage': usage} + result[name] = {'size': size, 'usage': usage, 'pool_lv': pool_lv, + 'attr': attr} return result @@ -416,16 +410,34 @@ class ThinVolume(qubes.storage.Volume): def pool_exists(pool_id): ''' Return true if pool exists ''' - cmd = ['pool', pool_id] - return qubes_lvm(cmd) + try: + vol_info = size_cache[pool_id] + return vol_info['attr'][0] == 't' + except KeyError: + return False def qubes_lvm(cmd, log=logging.getLogger('qube.storage.lvm')): - ''' Call :program:`qubes-lvm` to execute an LVM operation ''' - # TODO Refactor this ones the udev groups gets fixed and we don't need root - # for operations on lvm devices - cmd = ['sudo', 'qubes-lvm'] + cmd - p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + ''' Call :program:`lvm` to execute an LVM operation ''' + action = cmd[0] + if action == 'remove': + lvm_cmd = ['lvremove', '-f', cmd[1]] + elif action == 'clone': + lvm_cmd = ['lvcreate', '-kn', '-ay', '-s', cmd[1], '-n', cmd[2]] + elif action == 'create': + lvm_cmd = ['lvcreate', '-T', cmd[1], '-kn', '-ay', '-n', cmd[2], '-V', + str(cmd[3]) + 'B'] + elif action == 'extend': + size = int(cmd[2]) / (1000 * 1000) + lvm_cmd = ["lvextend", "-L%s" % size, cmd[1]] + else: + raise NotImplementedError('unsupported action: ' + action) + if os.getuid() != 0: + cmd = ['sudo', 'lvm'] + lvm_cmd + else: + cmd = ['lvm'] + lvm_cmd + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + close_fds=True) out, err = p.communicate() return_code = p.returncode if out: diff --git a/qubes/tools/qubes_lvm.py b/qubes/tools/qubes_lvm.py deleted file mode 100644 index 8ad9627c..00000000 --- a/qubes/tools/qubes_lvm.py +++ /dev/null @@ -1,277 +0,0 @@ -#!/usr/bin/python2 -# -# The Qubes OS Project, http://www.qubes-os.org -# -# Copyright (C) 2016 Bahtiar `kalkin-` Gadimov -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -# -''' Manage pools and volumes managed by the 'lvm_thin' driver. ''' - -from __future__ import print_function - -import argparse -import logging -import subprocess -import sys -import time -import lvm # pylint: disable=import-error - -log = logging.getLogger('qubes.storage.lvm') - - -def pool_exists(args): - """ Check if given name is an lvm thin volume. """ - # TODO Implement a faster and proper working version pool_exists - vg_name, thin_pool_name = args.pool_id.split('/', 1) - volume_group = lvm.vgOpen(vg_name) - for p in volume_group.listLVs(): - if p.getAttr()[0] == 't' and p.getName() == thin_pool_name: - volume_group.close() - return True - - volume_group.close() - return False - - -def volume_exists(volume): - """ Check if the given volume exists and is a thin volume """ - log.debug("Checking if the %s thin volume exists", volume) - assert volume is not None - vg_name, volume_name = volume.split('/', 1) - volume_group = lvm.vgOpen(vg_name) - for p in volume_group.listLVs(): - if p.getAttr()[0] == 'V' and p.getName() == volume_name: - volume_group.close() - return True - - volume_group.close() - return False - - -def remove_volume(args): - """ Tries to remove the specified logical volume. - - If the removal fails it will try up to 3 times waiting 1, 2 and 3 - seconds between tries. Most of the time this function fails if some - process still has the volume locked. - """ - img = args.name - if not volume_exists(img): - log.info("Expected to remove %s, but volume does not exist", img) - return - - tries = 1 - successful = False - cmd = ['lvremove', '-f', img] - - while tries <= 3 and not successful: - log.info("Trying to remove LVM %s", img) - try: - output = subprocess.check_output(cmd, stderr=subprocess.STDOUT) - log.debug(output) - successful = True - except subprocess.CalledProcessError: - successful = False - - if successful: - break - else: - time.sleep(tries) - tries += 1 - - if not successful: - log.error('Could not remove volume ' + img) - - -def clone_volume(args): - """ Calls lvcreate and creates new snapshot. """ - old = args.source - new_name = args.destination - cmd = ["lvcreate", "-kn", "-ay", "-s", old, "-n", new_name] - return subprocess.call(cmd) - - -def new_volume(args): - ''' Creates a new volume in the specified thin pool, formated with ext4 ''' - - thin_pool = args.pool_id - name = args.name - size = args.size - log.info('Creating new Thin LVM %s in %s VG %s bytes', name, thin_pool, - size) - cmd = ['lvcreate', '-T', thin_pool, '-kn', '-ay', '-n', name, '-V', - str(size) + 'B'] - - return subprocess.call(cmd) - - -def rename_volume(old_name, new_name): - ''' Rename volume ''' - log.debug("Renaming LVM %s to %s ", old_name, new_name) - retcode = subprocess.call(["lvrename", old_name, new_name]) - if retcode != 0: - raise IOError("Error renaming LVM %s to %s " % (old_name, new_name)) - return new_name - - -def extend_volume(args): - ''' Extends an existing lvm volume. Note this works on any lvm volume not - only on thin volumes. - ''' - vid = args.name - size = int(args.size) / (1000 * 1000) - log.debug("Extending LVM %s to %s", vid, size) - cmd = ["lvextend", "-L%s" % size, vid] - log.debug(cmd) - retcode = subprocess.call(cmd) - if retcode != 0: - raise IOError("Error extending LVM %s to %s " % (vid, size)) - return 0 - - -def init_pool_parser(sub_parsers): - ''' Initialize pool subparser ''' - pool_parser = sub_parsers.add_parser( - 'pool', - help="Exit with exit code 0 if pool exists") - pool_parser.add_argument('pool_id', metavar='VG/POOL', - help="volume_group/pool_name") - pool_parser.set_defaults(func=pool_exists) - - -def init_new_parser(sub_parsers): - ''' Initialize the 'new' subparser ''' - new_parser = sub_parsers.add_parser( - 'create', - help='Creates a new thin ThinPoolLogicalVolume') - new_parser.add_argument('pool_id', metavar='VG/POOL', - help="volume_group/pool_name") - - new_parser.add_argument('name', - help='name of the new ThinPoolLogicalVolume') - new_parser.add_argument( - 'size', help='size in bytes of the new ThinPoolLogicalVolume') - - new_parser.set_defaults(func=new_volume) - - -def init_import_parser(sub_parsers): - ''' Initialize import subparser ''' - import_parser = sub_parsers.add_parser( - 'import', - help='sparse copy data from stdin to a thin volume') - import_parser.add_argument('name', metavar='VG/VID', - help='volume_group/volume_name') - import_parser.set_defaults(func=import_volume) - -def init_clone_parser(sub_parsers): - ''' Initialize clone subparser ''' - clone_parser = sub_parsers.add_parser( - 'clone', - help='sparse copy data from stdin to a thin volume') - clone_parser.add_argument('source', metavar='VG/VID', - help='volume_group/volume_name') - clone_parser.add_argument('destination', metavar='VG/VID', - help='volume_group/volume_name') - clone_parser.set_defaults(func=clone_volume) - -def import_volume(args): - ''' Imports from stdin to a thin volume ''' - name = args.name - src = sys.stdin - blk_size = 4096 - zeros = '\x00' * blk_size - dst_path = '/dev/%s' % name - with open(dst_path, 'wb') as dst: - while True: - tmp = src.read(blk_size) - if not tmp: - break - elif tmp == zeros: - dst.seek(blk_size, 1) - else: - dst.write(tmp) - - -def list_volumes(args): - ''' lists volumes ''' - vg_name, _ = args.name.split('/') - volume_group = lvm.vgOpen(vg_name) - for p in volume_group.listLVs(): - if p.getAttr()[0] == 'V': - print(vg_name + "/" + p.getName() + ' ' + p.getAttr()) - volume_group.close() - - -def init_volumes_parser(sub_parsers): - ''' Initialize volumes subparser ''' - parser = sub_parsers.add_parser('volumes', - help='list volumes in a pool') - parser.add_argument('name', metavar='VG/THIN_POOL', - help='volume_group/thin_pool_name') - parser.set_defaults(func=list_volumes) - - -def init_remove_parser(sub_parsers): - ''' Initialize remove subparser ''' - remove_parser = sub_parsers.add_parser('remove', - help='Removes a LogicalVolume') - remove_parser.add_argument('name', metavar='VG/VID', - help='volume_group/volume_name') - remove_parser.set_defaults(func=remove_volume) - - -def init_extend_parser(sub_parsers): - ''' Initialize extend subparser ''' - extend_parser = sub_parsers.add_parser('extend', - help='extends a LogicalVolume') - extend_parser.add_argument('name', metavar='VG/VID', - help='volume_group/volume_name') - extend_parser.set_defaults(func=extend_volume) - extend_parser.add_argument( - 'size', help='size in bytes of the new ThinPoolLogicalVolume') - - -def get_parser(): - '''Create :py:class:`argparse.ArgumentParser` suitable for - :program:`qubes-lvm`. - ''' - parser = argparse.ArgumentParser(description=__doc__) - # pylint: disable=protected-access - parser.register('action', 'parsers', argparse._SubParsersAction) - sub_parsers = parser.add_subparsers( - title='commands', - description="For more information see qubes-lvm command -h", - dest='command') - init_clone_parser(sub_parsers) - init_extend_parser(sub_parsers) - init_import_parser(sub_parsers) - init_new_parser(sub_parsers) - init_pool_parser(sub_parsers) - init_remove_parser(sub_parsers) - init_volumes_parser(sub_parsers) - - return parser - - -def main(args=None): - '''Main routine of :program:`qubes-lvm`.''' - args = get_parser().parse_args(args) - return args.func(args) - - -if __name__ == '__main__': - sys.exit(main()) diff --git a/rpm_spec/core-dom0.spec b/rpm_spec/core-dom0.spec index ecfd4391..45fd4012 100644 --- a/rpm_spec/core-dom0.spec +++ b/rpm_spec/core-dom0.spec @@ -250,7 +250,6 @@ fi %{python_sitelib}/qubes/tools/qubes_monitor_layout_notify.py* %{python_sitelib}/qubes/tools/qubes_prefs.py* %{python_sitelib}/qubes/tools/qvm_block.py* -%{python_sitelib}/qubes/tools/qubes_lvm.py* %{python_sitelib}/qubes/tools/qvm_backup.py* %{python_sitelib}/qubes/tools/qvm_backup_restore.py* %{python_sitelib}/qubes/tools/qvm_create.py*