#
# 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())