From badc58837a07fb2244e89fde89990ee8e0df670d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Wed, 2 Nov 2016 06:28:41 +0100 Subject: [PATCH] Add qvm-template-postprocess tool This is intended to call to finish template installation/removal. Template RPM package is basically container for root.img, nothing more. Other parts needs to be generated after root.img extraction. Previously it was open coded in rpm post-install script, but lets keep it as qvm tool to ease supporting multiple version in template builder QubesOS/qubes-issues#2412 --- qubes/tools/qvm_template_postprocess.py | 163 ++++++++++++++++++++++++ rpm_spec/core-dom0.spec | 1 + 2 files changed, 164 insertions(+) create mode 100644 qubes/tools/qvm_template_postprocess.py diff --git a/qubes/tools/qvm_template_postprocess.py b/qubes/tools/qvm_template_postprocess.py new file mode 100644 index 00000000..1f2f9a01 --- /dev/null +++ b/qubes/tools/qvm_template_postprocess.py @@ -0,0 +1,163 @@ +#!/usr/bin/python2 -O +# vim: fileencoding=utf-8 + +# +# The Qubes OS Project, https://www.qubes-os.org/ +# +# Copyright (C) 2016 Marek Marczykowski-Górecki +# +# +# 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. + +import argparse +import glob +import os + +import shutil +import subprocess + +import sys + +import qubes +import qubes.tools + +parser = qubes.tools.QubesArgumentParser( + description='Postprocess template package, for internal use only') +parser.add_argument('--really', action='store_true', default=False, + help=argparse.SUPPRESS) +parser.add_argument('action', choices=['post-install', 'pre-remove'], + help='Action to perform') +parser.add_argument('name', action='store', + help='Template name') +parser.add_argument('dir', action='store', + help='Template directory') + + +def move_if_exists(source, dest_dir): + if os.path.exists(source): + shutil.move(source, os.path.join(dest_dir, os.path.basename(source))) + + +def import_data(source_dir, vm): + # FIXME: this abuses volume export() for importing purposes + root_path = os.path.join(source_dir, 'root.img') + target_path = vm.storage.export('root') + if os.path.exists(root_path + '.part.00'): + input_files = glob.glob(root_path + '.*') + cat = subprocess.Popen(['cat'] + sorted(input_files), + stdout=subprocess.PIPE) + tar = subprocess.Popen(['tar', 'xSOf', '-'], + stdin=cat.stdout, + stdout=open(target_path, 'w')) + if tar.wait() != 0: + raise qubes.exc.QubesException('root.img extraction failed') + if cat.wait() != 0: + raise qubes.exc.QubesException('root.img extraction failed') + elif os.path.exists(root_path): + subprocess.check_call( + ['dd', 'if='+root_path, 'of='+target_path, 'conv=sparse'] + ) + + move_if_exists(os.path.join(source_dir, 'whitelisted-appmenus.list'), + vm.dir_path) + move_if_exists(os.path.join(source_dir, 'vm-whitelisted-appmenus.list'), + vm.dir_path) + move_if_exists(os.path.join(source_dir, 'netvm-whitelisted-appmenus.list'), + vm.dir_path) + + shutil.rmtree(source_dir) + + +def post_install(args): + root_path = os.path.join(args.dir, 'root.img') + if os.path.exists(root_path + '.part.00'): + # get just file root_size from the tar header + p = subprocess.Popen(['tar', 'tvf', root_path + '.part.00'], + stdout=subprocess.PIPE, stderr=open(os.devnull, 'w')) + (stdout, _) = p.communicate() + # -rw-r--r-- 0/0 1073741824 1970-01-01 01:00 root.img + root_size = int(stdout.split()[2]) + elif os.path.exists(root_path): + root_size = os.path.getsize(root_path) + else: + raise qubes.exc.QubesException('root.img not found') + volume_config = {'root': {'size': root_size}} + + # TODO: add lock=True + app = args.app + reinstall = False + try: + # reinstall + vm = app.domains[args.name] + reinstall = True + except KeyError: + vm = app.add_new_vm(qubes.vm.templatevm.TemplateVM, + name=args.name, + label=qubes.config.defaults['template_label'], + volume_config=volume_config) + + # vm.create_on_disk() need to create the directory on its own, move it + # away for from its way + if vm.dir_path == args.dir: + shutil.move(args.dir, + os.path.join(qubes.config.qubes_base_dir, 'tmp-' + args.name)) + args.dir = os.path.join(qubes.config.qubes_base_dir, 'tmp-' + args.name) + if reinstall: + vm.remove_from_disk() + vm.create_on_disk() + vm.log.info('Importing data') + import_data(args.dir, vm) + app.save() + + # TODO: retrieve appmenus + + return 0 + + +def pre_remove(args): + # TODO: add lock=True + app = args.app + try: + tpl = app.domains[args.name] + except KeyError: + parser.error('Qube with this name do not exist') + return 1 + for appvm in app.domains: + if hasattr(appvm, 'template') and appvm.template == tpl: + parser.error('Qube {} use this template'.format(appvm.name)) + return 1 + + del app.domains[args.name] + tpl.remove_from_disk() + app.save() + return 0 + + +def main(args=None): + args = parser.parse_args(args) + if not args.really: + parser.error('Do not call this tool directly.') + return 1 + if args.action == 'post-install': + return post_install(args) + elif args.action == 'pre-remove': + pre_remove(args) + else: + parser.error('Unknown action') + return 1 + return 0 + +if __name__ == '__main__': + sys.exit(main()) \ No newline at end of file diff --git a/rpm_spec/core-dom0.spec b/rpm_spec/core-dom0.spec index 8cb4a2dd..5ed3a4ca 100644 --- a/rpm_spec/core-dom0.spec +++ b/rpm_spec/core-dom0.spec @@ -269,6 +269,7 @@ fi %{python_sitelib}/qubes/tools/qvm_shutdown.py* %{python_sitelib}/qubes/tools/qvm_start.py* %{python_sitelib}/qubes/tools/qvm_template_commit.py* +%{python_sitelib}/qubes/tools/qvm_template_postprocess.py* %{python_sitelib}/qubes/tools/qvm_unpause.py* %dir %{python_sitelib}/qubes/ext