#!/usr/bin/env python

# Copyright 2017 The Crashpad Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Helper script to [re]start or stop a helper Fuchsia QEMU instance to be used
for running tests without a device.
"""

from __future__ import print_function

import getpass
import os
import random
import signal
import subprocess
import sys
import tempfile
import time

try:
  from subprocess import DEVNULL
except ImportError:
  DEVNULL = open(os.devnull, 'r+b')

CRASHPAD_ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)),
                             os.pardir)


def _Stop(pid_file):
  if os.path.isfile(pid_file):
    with open(pid_file, 'rb') as f:
      pid = int(f.read().strip())
    try:
      os.kill(pid, signal.SIGTERM)
    except:
      print('Unable to kill pid %d, continuing' % pid, file=sys.stderr)
    os.unlink(pid_file)


def _CheckForTun():
  """Check for networking. TODO(scottmg): Currently, this is Linux-specific.
  """
  returncode = subprocess.call(
      ['tunctl', '-b', '-u', getpass.getuser(), '-t', 'qemu'],
      stdout=DEVNULL, stderr=DEVNULL)
  if returncode != 0:
    print('To use QEMU with networking on Linux, configure TUN/TAP. See:',
          file=sys.stderr)
    print('  https://fuchsia.googlesource.com/zircon/+/HEAD/docs/qemu.md#enabling-networking-under-qemu-x86_64-only',
          file=sys.stderr)
    return 2
  return 0


def _Start(pid_file):
  tun_result = _CheckForTun()
  if tun_result != 0:
    return tun_result

  arch = 'mac-amd64' if sys.platform == 'darwin' else 'linux-amd64'
  fuchsia_dir = os.path.join(CRASHPAD_ROOT, 'third_party', 'fuchsia')
  qemu_path = os.path.join(fuchsia_dir, 'qemu', arch, 'bin',
                           'qemu-system-x86_64')
  kernel_data_dir = os.path.join(fuchsia_dir, 'sdk', arch, 'target', 'x86_64')
  kernel_path = os.path.join(kernel_data_dir, 'zircon.bin')
  initrd_path = os.path.join(kernel_data_dir, 'bootdata.bin')

  mac_tail = ':'.join('%02x' % random.randint(0, 255) for x in range(3))
  instance_name = 'crashpad_qemu_' + \
      ''.join(chr(random.randint(ord('A'), ord('Z'))) for x in range(8))

  # These arguments are from the Fuchsia repo in zircon/scripts/run-zircon.
  popen = subprocess.Popen([
    qemu_path,
    '-m', '2048',
    '-nographic',
    '-kernel', kernel_path,
    '-initrd', initrd_path,
    '-smp', '4',
    '-serial', 'stdio',
    '-monitor', 'none',
    '-machine', 'q35',
    '-cpu', 'host,migratable=no',
    '-enable-kvm',
    '-netdev', 'type=tap,ifname=qemu,script=no,downscript=no,id=net0',
    '-device', 'e1000,netdev=net0,mac=52:54:00:' + mac_tail,
    '-append', 'TERM=dumb zircon.nodename=' + instance_name,
  ], stdin=DEVNULL, stdout=DEVNULL, stderr=DEVNULL)

  with open(pid_file, 'wb') as f:
    f.write('%d\n' % popen.pid)

  for i in range(10):
    netaddr_path = os.path.join(fuchsia_dir, 'sdk', arch, 'tools', 'netaddr')
    if subprocess.call([netaddr_path, '--nowait', instance_name],
                       stdout=open(os.devnull), stderr=open(os.devnull)) == 0:
      break
    time.sleep(.5)
  else:
    print('instance did not respond after start', file=sys.stderr)
    return 1

  return 0


def main(args):
  if len(args) != 1 or args[0] not in ('start', 'stop'):
    print('usage: run_fuchsia_qemu.py start|stop', file=sys.stderr)
    return 1

  command = args[0]

  pid_file = os.path.join(tempfile.gettempdir(), 'crashpad_fuchsia_qemu_pid')
  _Stop(pid_file)
  if command == 'start':
    return _Start(pid_file)

  return 0


if __name__ == '__main__':
  sys.exit(main(sys.argv[1:]))
