普通文本  |  200行  |  6.8 KB

# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""Chromoting helper to install/uninstall host and replace pref pane."""

import abc
import os
import shutil
import sys
import subprocess


class ChromotingHelper(object):
  """Chromoting helper base class."""
  __metaclass__ = abc.ABCMeta

  @abc.abstractmethod
  def InstallHost(self, bin_dir):
    """Installs the chromoting host"""
    return

  @abc.abstractmethod
  def UninstallHost(self, bin_dir):
    """Uninstalls the chromoting host"""
    return


class ChromotingHelperMac(ChromotingHelper):
  """Chromoting Helper class for Mac.

  Installs/uninstalls host and replace the pref pane for testing purpose.
  """

  def InstallHost(self, bin_dir):
    """Installs host on Mac."""
    assert os.geteuid() == 0, 'Need superuser privileges'

    # Run most of the steps here with login user
    login_uid = os.getuid()
    os.seteuid(login_uid)

    # Change the working dir to the dir that has the host zip file
    current_dir = os.getcwd()
    pyautolib_dir = os.path.dirname(os.path.abspath(__file__))
    os.chdir(bin_dir)
    host_dir = 'remoting-me2me-host-mac'
    output_dir = os.path.join(host_dir, 'output')

    # Remove remoting-me2me-host-mac dir just in case
    shutil.rmtree(host_dir, True)

    # Unzip the host archive and prepare the files/dirs
    subprocess.call('unzip remoting-me2me-host-mac.zip', shell=True)
    subprocess.call('mkdir ' + output_dir, shell=True)

    # Prepare security identity for code signing purpose
    os.seteuid(0)
    key_chain = '/Library/Keychains/ChromotingTest'
    password = '1111'
    key = os.path.join(pyautolib_dir, 'chromoting_key.p12')
    cert = os.path.join(pyautolib_dir, 'chromoting_cert.p12')
    subprocess.call(['security', 'delete-keychain', key_chain])
    subprocess.call(['security', 'create-keychain', '-p',
                     password, key_chain])
    subprocess.call(['security', 'import', key,
                     '-k', key_chain, '-P', password, '-A'])
    subprocess.call(['security', 'import', cert,
                     '-k', key_chain, '-P', password])
    os.seteuid(login_uid)

    # Sign the host
    do_signing = os.path.join(host_dir, 'do_signing.sh')
    subprocess.call(do_signing + ' ' + output_dir + ' ' + host_dir + ' ' +
                    key_chain + ' "Chromoting Test"', shell=True)

    # Remove security identify
    os.seteuid(0)
    subprocess.call(['security', 'delete-keychain', key_chain])
    os.seteuid(login_uid)

    # Figure out the dmg name
    version = ""
    for output_file in os.listdir(output_dir):
      if output_file.endswith('.dmg'):
        version = os.path.basename(output_file)[len('ChromotingHost-'):-4]

    # Mount before installation
    dmg = os.path.join(output_dir, 'ChromotingHost-' + version + '.dmg')
    subprocess.call('hdiutil' + ' mount ' + dmg, shell=True)

    # Install host
    os.seteuid(0)
    mpkg = os.path.join('/Volumes', 'Chromoting Host ' + version,
                        'Chromoting Host.pkg')
    subprocess.call(['/usr/sbin/installer', '-pkg',
                     mpkg, '-target', '/'])
    os.seteuid(login_uid)

    # Unmount after installation
    mounted = os.path.join('/Volumes', 'Chromoting Host ' + version)
    subprocess.call('hdiutil detach "' + mounted + '"', shell=True)

    # Clean up remoting-me2me-host-mac dir
    shutil.rmtree(host_dir, True)

    # Resume the original working dir
    os.chdir(current_dir)

  def UninstallHost(self, bin_dir):
    """Uninstalls host on Mac."""
    assert os.geteuid() == 0, 'Need superuser privileges'
    uninstall_app = os.path.join('/', 'Applications',
                                 'Chromoting Host Uninstaller.app',
                                 'Contents', 'MacOS',
                                 'remoting_host_uninstaller')
    subprocess.call([uninstall_app, '--no-ui'])

  def ReplacePrefPaneMac(self, operation):
    """Constructs mock pref pane to replace the actual pref pane on Mac."""
    assert os.geteuid() == 0, 'Need superuser privileges'

    pref_pane_dir = os.path.join('/Library', 'PreferencePanes')

    mock_pref_pane = os.path.join(pref_pane_dir, 'mock_pref_pane')
    pref_pane = os.path.join(pref_pane_dir,
                             'org.chromium.chromoting.prefPane')
    mock_pref_pane_python = os.path.join(
        os.path.dirname(os.path.abspath(__file__)),
        'mock_pref_pane.py')

    # When the symlink from real pref pane to mock pref pane exists,
    # mock pref pane will be modified to be a dir when the host is installed.
    # After the host is installed and mock pref pane is modified to be a file,
    # it will be a file until next host installation.
    if os.path.isdir(mock_pref_pane):
      shutil.rmtree(mock_pref_pane, True)
    elif os.path.isfile(mock_pref_pane):
      os.remove(mock_pref_pane)

    mock_pref_pane_file = open(mock_pref_pane, 'w')
    mock_pref_pane_file.write('#!/bin/bash\n')
    mock_pref_pane_file.write('\n')
    mock_pref_pane_file.write('suid-python'  +
                              ' ' + mock_pref_pane_python + ' ' + operation)
    mock_pref_pane_file.close()

    subprocess.call(['chmod', 'a+x', mock_pref_pane])

    # The real pref pane is a dir if the host is installed on a clean machine.
    # Once the test is run on the machine, real pref pane will be replaced to
    # a symlink.
    if os.path.isdir(pref_pane):
      shutil.rmtree(pref_pane, True)
    elif os.path.isfile(pref_pane):
      os.remove(pref_pane)

    subprocess.call(['ln', '-s', mock_pref_pane, pref_pane])


class ChromotingHelperWindows(ChromotingHelper):
  """Chromoting Helper class for Windows for installing/uninstalling host."""

  def InstallHost(self, bin_dir):
    """Installs host on Windows."""
    host_msi = os.path.join(bin_dir, 'chromoting.msi')
    subprocess.Popen(['msiexec', '/i', host_msi, '/passive']).wait()

  def UninstallHost(self, bin_dir):
    """Uninstalls host on Windows."""
    host_msi = os.path.join(bin_dir, 'chromoting.msi')
    subprocess.Popen(['msiexec', '/x', host_msi, '/passive']).wait()


def Main():
  """Main function to dispatch operations."""
  assert sys.platform.startswith('win') or \
      sys.platform.startswith('darwin'), \
      'Only support Windows and Mac'

  if sys.platform.startswith('win'):
    helper = ChromotingHelperWindows()
  elif sys.platform.startswith('darwin'):
    helper = ChromotingHelperMac()

  if sys.argv[1] == 'install':
    helper.InstallHost(sys.argv[2])
  elif sys.argv[1] == 'uninstall':
    helper.UninstallHost(sys.argv[2])
  elif sys.argv[1] in ['enable', 'disable', 'changepin']:
    assert sys.platform.startswith('darwin'), \
      'Replacing pref pane is Mac specific'
    helper.ReplacePrefPaneMac(sys.argv[1])
  else:
    print >>sys.stderr, 'Invalid syntax'
    return 1


if __name__ == '__main__':
  Main()