123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194 |
- #
- # 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 grp
- 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()
- if os.getuid() == 0:
- # fix permissions, do it only here (not after starting the VM),
- # because we're running as root only at early installation phase,
- # when offline mode is enabled anyway - otherwise main() would switch
- # to non-root user
- try:
- qubes_group = grp.getgrnam('qubes')
- for dirpath, _, filenames in os.walk(vm.dir_path):
- os.chown(dirpath, -1, qubes_group.gr_gid)
- os.chmod(dirpath, 0o2775)
- for name in filenames:
- filename = os.path.join(dirpath, name)
- os.chown(filename, -1, qubes_group.gr_gid)
- os.chmod(filename, 0o664)
- except KeyError:
- raise qubes.exc.QubesException('\'qubes\' group missing')
- if not app.vmm.offline_mode:
- # just created, so no need to save previous value - we know what it was
- vm.netvm = None
- vm.start(start_guid=False)
- vm.fire_event('template-postinstall')
- vm.shutdown(wait=True)
- vm.netvm = qubes.property.DEFAULT
- 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):
- if os.getuid() == 0:
- try:
- qubes_group = grp.getgrnam('qubes')
- prefix_cmd = ['runuser', '-u', qubes_group.gr_mem[0], '--']
- os.execvp('runuser', prefix_cmd + sys.argv)
- except (KeyError, IndexError):
- # When group or user do not exist yet, continue as root. This
- # probably also means we're still in installer, so some actions
- # will not be taken anyway (because of running in chroot ->
- # offline mode).
- pass
- 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())
|