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
This commit is contained in:
Marek Marczykowski-Górecki 2016-11-02 06:28:41 +01:00
parent 408606e0f0
commit badc58837a
No known key found for this signature in database
GPG Key ID: 063938BA42CFA724
2 changed files with 164 additions and 0 deletions

View File

@ -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
# <marmarek@invisiblethingslab.com>
#
# 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())

View File

@ -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