12a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#!/usr/bin/python
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Copyright (c) 2012 The Chromium Authors. All rights reserved.
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# found in the LICENSE file.
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Virtual Me2Me implementation.  This script runs and manages the processes
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# required for a Virtual Me2Me desktop, which are: X server, X desktop
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# session, and Host process.
95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# This script is intended to run continuously as a background daemon
105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# process, running under an ordinary (non-root) user account.
115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import atexit
135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import errno
147d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)import fcntl
155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import getpass
1658537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)import grp
175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import hashlib
185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import json
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import logging
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import optparse
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import os
222a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import pipes
23f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)import platform
242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import psutil
255d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import platform
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import signal
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import socket
285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import subprocess
295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import sys
305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import tempfile
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import time
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import uuid
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)LOG_FILE_ENV_VAR = "CHROME_REMOTE_DESKTOP_LOG_FILE"
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# This script has a sensible default for the initial and maximum desktop size,
372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# which can be overridden either on the command-line, or via a comma-separated
382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# list of sizes in this environment variable.
392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)DEFAULT_SIZES_ENV_VAR = "CHROME_REMOTE_DESKTOP_DEFAULT_DESKTOP_SIZES"
402a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
41cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)# By default, provide a maximum size that is large enough to support clients
42cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)# with large or multiple monitors. This is a comma-separated list of
43cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)# resolutions that will be made available if the X server supports RANDR. These
44cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)# defaults can be overridden in ~/.profile.
45f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)DEFAULT_SIZES = "1600x1200,3840x2560"
462a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
47cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)# If RANDR is not available, use a smaller default size. Only a single
48cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)# resolution is supported in this case.
49cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)DEFAULT_SIZE_NO_RANDR = "1600x1200"
50cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)SCRIPT_PATH = sys.path[0]
525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
53a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)IS_INSTALLED = (os.path.basename(sys.argv[0]) != 'linux_me2me_host.py')
545d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
555d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)if IS_INSTALLED:
565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  HOST_BINARY_NAME = "chrome-remote-desktop-host"
575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)else:
585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  HOST_BINARY_NAME = "remoting_me2me_host"
595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)CHROME_REMOTING_GROUP_NAME = "chrome-remote-desktop"
615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)HOME_DIR = os.environ["HOME"]
635d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)CONFIG_DIR = os.path.join(HOME_DIR, ".config/chrome-remote-desktop")
645d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)SESSION_FILE_PATH = os.path.join(HOME_DIR, ".chrome-remote-desktop-session")
655c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiuSYSTEM_SESSION_FILE_PATH = "/etc/chrome-remote-desktop-session"
665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)X_LOCK_FILE_TEMPLATE = "/tmp/.X%d-lock"
685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)FIRST_X_DISPLAY_NUMBER = 20
695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
702a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# Amount of time to wait between relaunching processes.
712a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)SHORT_BACKOFF_TIME = 5
722a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)LONG_BACKOFF_TIME = 60
735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
742a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# How long a process must run in order not to be counted against the restart
752a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# thresholds.
762a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)MINIMUM_PROCESS_LIFETIME = 60
775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
782a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# Thresholds for switching from fast- to slow-restart and for giving up
792a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# trying to restart entirely.
802a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)SHORT_BACKOFF_THRESHOLD = 5
812a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)MAX_LAUNCH_FAILURES = SHORT_BACKOFF_THRESHOLD + 10
825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Globals needed by the atexit cleanup() handler.
845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)g_desktops = []
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)g_host_hash = hashlib.md5(socket.gethostname()).hexdigest()
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
87cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
885d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)def is_supported_platform():
895d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  # Always assume that the system is supported if the config directory or
905d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  # session file exist.
915c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (os.path.isdir(CONFIG_DIR) or os.path.isfile(SESSION_FILE_PATH) or
925c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      os.path.isfile(SYSTEM_SESSION_FILE_PATH)):
935d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    return True
945d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
955d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  # The host has been tested only on Ubuntu.
965d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  distribution = platform.linux_distribution()
975d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  return (distribution[0]).lower() == 'ubuntu'
985d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
99cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
100cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)def get_randr_supporting_x_server():
101cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  """Returns a path to an X server that supports the RANDR extension, if this
102cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  is found on the system. Otherwise returns None."""
103cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  try:
104cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    xvfb = "/usr/bin/Xvfb-randr"
105cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if not os.path.exists(xvfb):
106cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      xvfb = locate_executable("Xvfb-randr")
107cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return xvfb
108cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  except Exception:
109cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return None
110cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
111cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class Config:
1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def __init__(self, path):
1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.path = path
1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.data = {}
1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.changed = False
1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def load(self):
1192a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    """Loads the config from file.
1202a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1212a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    Raises:
1222a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      IOError: Error reading data
1232a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      ValueError: Error parsing JSON
1242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    """
1252a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    settings_file = open(self.path, 'r')
1262a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    self.data = json.load(settings_file)
1272a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    self.changed = False
1282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    settings_file.close()
1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def save(self):
1312a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    """Saves the config to file.
1322a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1332a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    Raises:
1342a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      IOError: Error writing data
1352a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      TypeError: Error serialising JSON
1362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    """
1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if not self.changed:
1382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      return
1392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    old_umask = os.umask(0066)
1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    try:
1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      settings_file = open(self.path, 'w')
1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      settings_file.write(json.dumps(self.data, indent=2))
1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      settings_file.close()
1442a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      self.changed = False
1452a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    finally:
1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      os.umask(old_umask)
1472a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1482a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  def save_and_log_errors(self):
1492a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    """Calls self.save(), trapping and logging any errors."""
1502a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    try:
1512a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      self.save()
1522a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    except (IOError, TypeError) as e:
1532a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      logging.error("Failed to save config: " + str(e))
1545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def get(self, key):
1565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return self.data.get(key)
1575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def __getitem__(self, key):
1595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return self.data[key]
1605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def __setitem__(self, key, value):
1625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.data[key] = value
1635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.changed = True
1645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
165a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  def clear(self):
166a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)    self.data = {}
167868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    self.changed = True
1685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class Authentication:
1715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Manage authentication tokens for Chromoting/xmpp"""
1725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def __init__(self):
1745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.login = None
1755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.oauth_refresh_token = None
1765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def copy_from(self, config):
1785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Loads the config and returns false if the config is invalid."""
1795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    try:
1805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.login = config["xmpp_login"]
1815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.oauth_refresh_token = config["oauth_refresh_token"]
1825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    except KeyError:
1835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return False
1845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return True
1855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def copy_to(self, config):
1875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    config["xmpp_login"] = self.login
1885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    config["oauth_refresh_token"] = self.oauth_refresh_token
1895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class Host:
1925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """This manages the configuration for a host."""
1935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def __init__(self):
1955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.host_id = str(uuid.uuid1())
1965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.host_name = socket.gethostname()
1975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.host_secret_hash = None
1985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.private_key = None
1995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def copy_from(self, config):
2015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    try:
2025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.host_id = config["host_id"]
2035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.host_name = config["host_name"]
2045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.host_secret_hash = config.get("host_secret_hash")
2055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.private_key = config["private_key"]
2065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    except KeyError:
2075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return False
2085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return True
2095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def copy_to(self, config):
2115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    config["host_id"] = self.host_id
2125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    config["host_name"] = self.host_name
2135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    config["host_secret_hash"] = self.host_secret_hash
2145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    config["private_key"] = self.private_key
2155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class Desktop:
2185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Manage a single virtual desktop"""
2195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def __init__(self, sizes):
2215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.x_proc = None
2225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.session_proc = None
2235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.host_proc = None
2245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.child_env = None
2255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.sizes = sizes
2265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.pulseaudio_pipe = None
2275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.server_supports_exact_resize = False
2287d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    self.host_ready = False
229a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    self.ssh_auth_sockname = None
2305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    g_desktops.append(self)
2315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  @staticmethod
2335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def get_unused_display_number():
2345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Return a candidate display number for which there is currently no
2355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    X Server lock file"""
2365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    display = FIRST_X_DISPLAY_NUMBER
2375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    while os.path.exists(X_LOCK_FILE_TEMPLATE % display):
2385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      display += 1
2395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return display
2405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def _init_child_env(self):
2425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Create clean environment for new session, so it is cleanly separated from
2435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # the user's console X session.
2445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.child_env = {}
2452a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
2465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for key in [
2475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        "HOME",
2485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        "LANG",
2495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        "LOGNAME",
2505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        "PATH",
2515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        "SHELL",
2525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        "USER",
2535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        "USERNAME",
2545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        LOG_FILE_ENV_VAR]:
2555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if os.environ.has_key(key):
2565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.child_env[key] = os.environ[key]
2575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
258f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    # Ensure that the software-rendering GL drivers are loaded by the desktop
259f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    # session, instead of any hardware GL drivers installed on the system.
260f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    self.child_env["LD_LIBRARY_PATH"] = (
261f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)        "/usr/lib/%(arch)s-linux-gnu/mesa:"
262f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)        "/usr/lib/%(arch)s-linux-gnu/dri:"
263f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)        "/usr/lib/%(arch)s-linux-gnu/gallium-pipe" %
264f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)        { "arch": platform.machine() })
265f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
2662a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    # Read from /etc/environment if it exists, as it is a standard place to
2672a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    # store system-wide environment settings. During a normal login, this would
2682a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    # typically be done by the pam_env PAM module, depending on the local PAM
2692a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    # configuration.
2702a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    env_filename = "/etc/environment"
2712a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    try:
2722a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      with open(env_filename, "r") as env_file:
2732a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        for line in env_file:
2742a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          line = line.rstrip("\n")
2752a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          # Split at the first "=", leaving any further instances in the value.
2762a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          key_value_pair = line.split("=", 1)
2772a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          if len(key_value_pair) == 2:
2782a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            key, value = tuple(key_value_pair)
2792a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            # The file stores key=value assignments, but the value may be
2802a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            # quoted, so strip leading & trailing quotes from it.
2812a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            value = value.strip("'\"")
2822a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            self.child_env[key] = value
2832a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    except IOError:
2842a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      logging.info("Failed to read %s, skipping." % env_filename)
2852a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
2865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def _setup_pulseaudio(self):
2875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.pulseaudio_pipe = None
2885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # pulseaudio uses UNIX sockets for communication. Length of UNIX socket
2905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # name is limited to 108 characters, so audio will not work properly if
2915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # the path is too long. To workaround this problem we use only first 10
2925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # symbols of the host hash.
2935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    pulse_path = os.path.join(CONFIG_DIR,
2945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                              "pulseaudio#%s" % g_host_hash[0:10])
2955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if len(pulse_path) + len("/native") >= 108:
2965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      logging.error("Audio will not be enabled because pulseaudio UNIX " +
2975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    "socket path is too long.")
2985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return False
2995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    sink_name = "chrome_remote_desktop_session"
3015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    pipe_name = os.path.join(pulse_path, "fifo_output")
3025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    try:
3045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if not os.path.exists(pulse_path):
3055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        os.mkdir(pulse_path)
3065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    except IOError, e:
3075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      logging.error("Failed to create pulseaudio pipe: " + str(e))
3085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return False
3095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    try:
311c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      pulse_config = open(os.path.join(pulse_path, "daemon.conf"), "w")
312c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      pulse_config.write("default-sample-format = s16le\n")
313c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      pulse_config.write("default-sample-rate = 48000\n")
314c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      pulse_config.write("default-sample-channels = 2\n")
315c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      pulse_config.close()
316c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
317c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      pulse_script = open(os.path.join(pulse_path, "default.pa"), "w")
318c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      pulse_script.write("load-module module-native-protocol-unix\n")
319c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      pulse_script.write(
3205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          ("load-module module-pipe-sink sink_name=%s file=\"%s\" " +
3215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)           "rate=48000 channels=2 format=s16le\n") %
3225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          (sink_name, pipe_name))
323c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      pulse_script.close()
3245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    except IOError, e:
3255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      logging.error("Failed to write pulseaudio config: " + str(e))
3265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return False
3275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.child_env["PULSE_CONFIG_PATH"] = pulse_path
3295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.child_env["PULSE_RUNTIME_PATH"] = pulse_path
3305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.child_env["PULSE_STATE_PATH"] = pulse_path
3315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.child_env["PULSE_SINK"] = sink_name
3325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.pulseaudio_pipe = pipe_name
3335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return True
3355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
336a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  def _setup_gnubby(self):
337a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    self.ssh_auth_sockname = ("/tmp/chromoting.%s.ssh_auth_sock" %
338a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                              os.environ["USER"])
339a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
3405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def _launch_x_server(self, extra_x_args):
3412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    x_auth_file = os.path.expanduser("~/.Xauthority")
3422a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    self.child_env["XAUTHORITY"] = x_auth_file
3435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    devnull = open(os.devnull, "rw")
3445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    display = self.get_unused_display_number()
3452a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
3462a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    # Run "xauth add" with |child_env| so that it modifies the same XAUTHORITY
3472a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    # file which will be used for the X session.
3485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    ret_code = subprocess.call("xauth add :%d . `mcookie`" % display,
3492a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                               env=self.child_env, shell=True)
3505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if ret_code != 0:
3515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      raise Exception("xauth failed with code %d" % ret_code)
3525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    max_width = max([width for width, height in self.sizes])
3545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    max_height = max([height for width, height in self.sizes])
3555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
356cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    xvfb = get_randr_supporting_x_server()
357cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if xvfb:
3585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.server_supports_exact_resize = True
359cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    else:
3605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      xvfb = "Xvfb"
3615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.server_supports_exact_resize = False
3625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
363c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    # Disable the Composite extension iff the X session is the default
364c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    # Unity-2D, since it uses Metacity which fails to generate DAMAGE
365c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    # notifications correctly. See crbug.com/166468.
3667d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    x_session = choose_x_session()
367c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    if (len(x_session) == 2 and
368c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)        x_session[1] == "/usr/bin/gnome-session --session=ubuntu-2d"):
369c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      extra_x_args.extend(["-extension", "Composite"])
370c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
3715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    logging.info("Starting %s on display :%d" % (xvfb, display))
3725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    screen_option = "%dx%dx24" % (max_width, max_height)
3732a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    self.x_proc = subprocess.Popen(
3742a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        [xvfb, ":%d" % display,
3752a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)         "-auth", x_auth_file,
3762a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)         "-nolisten", "tcp",
3772a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)         "-noreset",
3782a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)         "-screen", "0", screen_option
3792a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        ] + extra_x_args)
3805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if not self.x_proc.pid:
3815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      raise Exception("Could not start Xvfb.")
3825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.child_env["DISPLAY"] = ":%d" % display
3845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.child_env["CHROME_REMOTE_DESKTOP_SESSION"] = "1"
3855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3864e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    # Use a separate profile for any instances of Chrome that are started in
3874e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    # the virtual session. Chrome doesn't support sharing a profile between
3884e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    # multiple DISPLAYs, but Chrome Sync allows for a reasonable compromise.
3894e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    chrome_profile = os.path.join(CONFIG_DIR, "chrome-profile")
3904e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    self.child_env["CHROME_USER_DATA_DIR"] = chrome_profile
3914e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
392a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    # Set SSH_AUTH_SOCK to the file name to listen on.
393a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    if self.ssh_auth_sockname:
394a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      self.child_env["SSH_AUTH_SOCK"] = self.ssh_auth_sockname
395a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
3965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Wait for X to be active.
3975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for _test in range(5):
3985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      proc = subprocess.Popen("xdpyinfo", env=self.child_env, stdout=devnull)
3995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      _pid, retcode = os.waitpid(proc.pid, 0)
4005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if retcode == 0:
4015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        break
4025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      time.sleep(0.5)
4035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if retcode != 0:
4045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      raise Exception("Could not connect to Xvfb.")
4055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    else:
4065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      logging.info("Xvfb is active.")
4075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # The remoting host expects the server to use "evdev" keycodes, but Xvfb
4095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # starts configured to use the "base" ruleset, resulting in XKB configuring
4105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # for "xfree86" keycodes, and screwing up some keys. See crbug.com/119013.
4115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Reconfigure the X server to use "evdev" keymap rules.  The X server must
4125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # be started with -noreset otherwise it'll reset as soon as the command
4135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # completes, since there are no other X clients running yet.
4145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    proc = subprocess.Popen("setxkbmap -rules evdev", env=self.child_env,
4155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                            shell=True)
4165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    _pid, retcode = os.waitpid(proc.pid, 0)
4175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if retcode != 0:
4185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      logging.error("Failed to set XKB to 'evdev'")
4195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Register the screen sizes if the X server's RANDR extension supports it.
4215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Errors here are non-fatal; the X server will continue to run with the
4225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # dimensions from the "-screen" option.
4235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for width, height in self.sizes:
4245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      label = "%dx%d" % (width, height)
4255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      args = ["xrandr", "--newmode", label, "0", str(width), "0", "0", "0",
4265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)              str(height), "0", "0", "0"]
4272a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      subprocess.call(args, env=self.child_env, stdout=devnull, stderr=devnull)
4285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      args = ["xrandr", "--addmode", "screen", label]
4292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      subprocess.call(args, env=self.child_env, stdout=devnull, stderr=devnull)
4305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Set the initial mode to the first size specified, otherwise the X server
4325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # would default to (max_width, max_height), which might not even be in the
4335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # list.
4345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    label = "%dx%d" % self.sizes[0]
4355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    args = ["xrandr", "-s", label]
4362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    subprocess.call(args, env=self.child_env, stdout=devnull, stderr=devnull)
4372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
4382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    # Set the physical size of the display so that the initial mode is running
4392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    # at approximately 96 DPI, since some desktops require the DPI to be set to
4402a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    # something realistic.
4412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    args = ["xrandr", "--dpi", "96"]
4422a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    subprocess.call(args, env=self.child_env, stdout=devnull, stderr=devnull)
4435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    devnull.close()
4455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def _launch_x_session(self):
4472a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    # Start desktop session.
4485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # The /dev/null input redirection is necessary to prevent the X session
4495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # reading from stdin.  If this code runs as a shell background job in a
4505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # terminal, any reading from stdin causes the job to be suspended.
4515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Daemonization would solve this problem by separating the process from the
4525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # controlling terminal.
4532a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    xsession_command = choose_x_session()
4542a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if xsession_command is None:
4552a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      raise Exception("Unable to choose suitable X session command.")
4562a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
4572a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    logging.info("Launching X session: %s" % xsession_command)
4582a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    self.session_proc = subprocess.Popen(xsession_command,
4595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                         stdin=open(os.devnull, "r"),
4605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                         cwd=HOME_DIR,
4615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                         env=self.child_env)
4625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if not self.session_proc.pid:
4635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      raise Exception("Could not start X session")
4645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def launch_session(self, x_args):
4665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._init_child_env()
4675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._setup_pulseaudio()
468a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    self._setup_gnubby()
4695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._launch_x_server(x_args)
4705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._launch_x_session()
4715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def launch_host(self, host_config):
4735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Start remoting host
474a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)    args = [locate_executable(HOST_BINARY_NAME), "--host-config=-"]
4755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if self.pulseaudio_pipe:
4765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      args.append("--audio-pipe-name=%s" % self.pulseaudio_pipe)
4775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if self.server_supports_exact_resize:
4785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      args.append("--server-supports-exact-resize")
479a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    if self.ssh_auth_sockname:
480a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      args.append("--ssh-auth-sockname=%s" % self.ssh_auth_sockname)
4817d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)
4827d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    # Have the host process use SIGUSR1 to signal a successful start.
4837d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    def sigusr1_handler(signum, frame):
4847d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)      _ = signum, frame
4857d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)      logging.info("Host ready to receive connections.")
4867d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)      self.host_ready = True
4877d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)      if (ParentProcessLogger.instance() and
4887d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)          False not in [desktop.host_ready for desktop in g_desktops]):
4897d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)        ParentProcessLogger.instance().release_parent()
4907d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)
4917d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    signal.signal(signal.SIGUSR1, sigusr1_handler)
4927d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    args.append("--signal-parent")
4937d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)
4945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.host_proc = subprocess.Popen(args, env=self.child_env,
4955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                      stdin=subprocess.PIPE)
4965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    logging.info(args)
4975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if not self.host_proc.pid:
4985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      raise Exception("Could not start Chrome Remote Desktop host")
4995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.host_proc.stdin.write(json.dumps(host_config.data))
5005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.host_proc.stdin.close()
5015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5036d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)def get_daemon_proc():
5042a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  """Checks if there is already an instance of this script running, and returns
5056d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)  a psutil.Process instance for it.
5065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5072a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  Returns:
5086d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)    A Process instance for the existing daemon process, or None if the daemon
5096d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)    is not running.
5105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """
5116d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)
5122a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  uid = os.getuid()
5132a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  this_pid = os.getpid()
5145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5150de6073388f4e2780db8536178b129cd8f6ab386Torne (Richard Coles)  # Support new & old psutil API. This is the right way to check, according to
5160de6073388f4e2780db8536178b129cd8f6ab386Torne (Richard Coles)  # http://grodola.blogspot.com/2014/01/psutil-20-porting.html
5170de6073388f4e2780db8536178b129cd8f6ab386Torne (Richard Coles)  if psutil.version_info >= (2, 0):
518a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch    psget = lambda x: x()
5190de6073388f4e2780db8536178b129cd8f6ab386Torne (Richard Coles)  else:
5200de6073388f4e2780db8536178b129cd8f6ab386Torne (Richard Coles)    psget = lambda x: x
521a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch
5222a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  for process in psutil.process_iter():
5232a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    # Skip any processes that raise an exception, as processes may terminate
5242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    # during iteration over the list.
5252a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    try:
5262a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      # Skip other users' processes.
527a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch      if psget(process.uids).real != uid:
5282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        continue
5295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      # Skip the process for this instance.
5312a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      if process.pid == this_pid:
5322a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        continue
5335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5342a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      # |cmdline| will be [python-interpreter, script-file, other arguments...]
535a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch      cmdline = psget(process.cmdline)
5362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      if len(cmdline) < 2:
5372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        continue
5382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      if cmdline[0] == sys.executable and cmdline[1] == sys.argv[0]:
5396d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)        return process
5400de6073388f4e2780db8536178b129cd8f6ab386Torne (Richard Coles)    except (psutil.NoSuchProcess, psutil.AccessDenied):
5412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      continue
5425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5436d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)  return None
5445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def choose_x_session():
5475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Chooses the most appropriate X session command for this system.
5485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Returns:
5505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    A string containing the command to run, or a list of strings containing
5515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    the executable program and its arguments, which is suitable for passing as
5525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    the first parameter of subprocess.Popen().  If a suitable session cannot
5535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    be found, returns None.
5545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """
5555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  XSESSION_FILES = [
5565d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    SESSION_FILE_PATH,
5575c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    SYSTEM_SESSION_FILE_PATH ]
5585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  for startup_file in XSESSION_FILES:
5595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    startup_file = os.path.expanduser(startup_file)
5605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if os.path.exists(startup_file):
5615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # Use the same logic that a Debian system typically uses with ~/.xsession
5625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # (see /etc/X11/Xsession.d/50x11-common_determine-startup), to determine
5635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # exactly how to run this file.
5645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if os.access(startup_file, os.X_OK):
5652a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        # "/bin/sh -c" is smart about how to execute the session script and
5662a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        # works in cases where plain exec() fails (for example, if the file is
5672a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        # marked executable, but is a plain script with no shebang line).
5682a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        return ["/bin/sh", "-c", pipes.quote(startup_file)]
5695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      else:
5705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        shell = os.environ.get("SHELL", "sh")
5715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return [shell, startup_file]
5725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Choose a session wrapper script to run the session. On some systems,
5745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # /etc/X11/Xsession fails to load the user's .profile, so look for an
5755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # alternative wrapper that is more likely to match the script that the
5765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # system actually uses for console desktop sessions.
5775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  SESSION_WRAPPERS = [
5785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    "/usr/sbin/lightdm-session",
5795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    "/etc/gdm/Xsession",
5805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    "/etc/X11/Xsession" ]
5815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  for session_wrapper in SESSION_WRAPPERS:
5825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if os.path.exists(session_wrapper):
5832a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      if os.path.exists("/usr/bin/unity-2d-panel"):
5842a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        # On Ubuntu 12.04, the default session relies on 3D-accelerated
5852a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        # hardware. Trying to run this with a virtual X display produces
5862a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        # weird results on some systems (for example, upside-down and
5872a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        # corrupt displays).  So if the ubuntu-2d session is available,
5882a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        # choose it explicitly.
5892a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        return [session_wrapper, "/usr/bin/gnome-session --session=ubuntu-2d"]
5902a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      else:
5912a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        # Use the session wrapper by itself, and let the system choose a
5922a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        # session.
593c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)        return [session_wrapper]
5942a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  return None
5955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def locate_executable(exe_name):
5985d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  if IS_INSTALLED:
5995d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    # If the script is running from its installed location, search the host
6005d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    # binary only in the same directory.
6015d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    paths_to_try = [ SCRIPT_PATH ]
6025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  else:
6035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    paths_to_try = map(lambda p: os.path.join(SCRIPT_PATH, p),
60423730a6e56a168d1879203e4b3819bb36e3d8f1fTorne (Richard Coles)                       [".", "../../../out/Debug", "../../../out/Release" ])
6055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  for path in paths_to_try:
6065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    exe_path = os.path.join(path, exe_name)
6075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if os.path.exists(exe_path):
6085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return exe_path
6095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  raise Exception("Could not locate executable '%s'" % exe_name)
6115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6137d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)class ParentProcessLogger(object):
6147d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)  """Redirects logs to the parent process, until the host is ready or quits.
6157d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)
6167d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)  This class creates a pipe to allow logging from the daemon process to be
6177d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)  copied to the parent process. The daemon process adds a log-handler that
6187d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)  directs logging output to the pipe. The parent process reads from this pipe
6197d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)  until and writes the content to stderr.  When the pipe is no longer needed
6207d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)  (for example, the host signals successful launch or permanent failure), the
6217d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)  daemon removes the log-handler and closes the pipe, causing the the parent
6227d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)  process to reach end-of-file while reading the pipe and exit.
6237d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)
6247d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)  The (singleton) logger should be instantiated before forking. The parent
6257d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)  process should call wait_for_logs() before exiting. The (grand-)child process
6267d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)  should call start_logging() when it starts, and then use logging.* to issue
6277d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)  log statements, as usual. When the child has either succesfully started the
6287d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)  host or terminated, it must call release_parent() to allow the parent to exit.
6297d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)  """
6307d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)
6317d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)  __instance = None
6327d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)
6337d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)  def __init__(self):
6347d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    """Constructor. Must be called before forking."""
6357d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    read_pipe, write_pipe = os.pipe()
6367d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    # Ensure write_pipe is closed on exec, otherwise it will be kept open by
6377d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    # child processes (X, host), preventing the read pipe from EOF'ing.
6387d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    old_flags = fcntl.fcntl(write_pipe, fcntl.F_GETFD)
6397d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    fcntl.fcntl(write_pipe, fcntl.F_SETFD, old_flags | fcntl.FD_CLOEXEC)
6407d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    self._read_file = os.fdopen(read_pipe, 'r')
6417d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    self._write_file = os.fdopen(write_pipe, 'a')
6427d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    self._logging_handler = None
6437d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    ParentProcessLogger.__instance = self
6447d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)
6457d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)  def start_logging(self):
6467d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    """Installs a logging handler that sends log entries to a pipe.
6477d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)
6487d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    Must be called by the child process.
6497d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    """
6507d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    self._read_file.close()
6517d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    self._logging_handler = logging.StreamHandler(self._write_file)
6527d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    logging.getLogger().addHandler(self._logging_handler)
6537d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)
6547d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)  def release_parent(self):
6557d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    """Uninstalls logging handler and closes the pipe, releasing the parent.
6567d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)
6577d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    Must be called by the child process.
6587d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    """
6597d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    if self._logging_handler:
6607d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)      logging.getLogger().removeHandler(self._logging_handler)
6617d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)      self._logging_handler = None
6627d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    if not self._write_file.closed:
6637d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)      self._write_file.close()
6647d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)
6657d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)  def wait_for_logs(self):
6667d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    """Waits and prints log lines from the daemon until the pipe is closed.
6677d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)
6687d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    Must be called by the parent process.
6697d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    """
6707d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    # If Ctrl-C is pressed, inform the user that the daemon is still running.
6717d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    # This signal will cause the read loop below to stop with an EINTR IOError.
6727d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    def sigint_handler(signum, frame):
6737d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)      _ = signum, frame
6747d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)      print >> sys.stderr, ("Interrupted. The daemon is still running in the "
6757d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)                            "background.")
6767d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)
6777d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    signal.signal(signal.SIGINT, sigint_handler)
6787d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)
6797d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    # Install a fallback timeout to release the parent process, in case the
6807d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    # daemon never responds (e.g. host crash-looping, daemon killed).
6817d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    # This signal will cause the read loop below to stop with an EINTR IOError.
6827d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    def sigalrm_handler(signum, frame):
6837d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)      _ = signum, frame
6847d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)      print >> sys.stderr, ("No response from daemon. It may have crashed, or "
6857d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)                            "may still be running in the background.")
6867d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)
6877d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    signal.signal(signal.SIGALRM, sigalrm_handler)
6887d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    signal.alarm(30)
6897d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)
6907d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    self._write_file.close()
6917d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)
6927d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    # Print lines as they're logged to the pipe until EOF is reached or readline
6937d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    # is interrupted by one of the signal handlers above.
6947d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    try:
6957d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)      for line in iter(self._read_file.readline, ''):
6967d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)        sys.stderr.write(line)
6977d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    except IOError as e:
6987d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)      if e.errno != errno.EINTR:
6997d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)        raise
7007d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    print >> sys.stderr, "Log file: %s" % os.environ[LOG_FILE_ENV_VAR]
7017d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)
7027d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)  @staticmethod
7037d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)  def instance():
7047d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    """Returns the singleton instance, if it exists."""
7057d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    return ParentProcessLogger.__instance
7067d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)
7077d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)
7087d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)def daemonize():
7095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Background this process and detach from controlling terminal, redirecting
7107d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)  stdout/stderr to a log file."""
7115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # TODO(lambroslambrou): Having stdout/stderr redirected to a log file is not
7135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # ideal - it could create a filesystem DoS if the daemon or a child process
7145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # were to write excessive amounts to stdout/stderr.  Ideally, stdout/stderr
7155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # should be redirected to a pipe or socket, and a process at the other end
7165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # should consume the data and write it to a logging facility which can do
7175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # data-capping or log-rotation. The 'logger' command-line utility could be
7185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # used for this, but it might cause too much syslog spam.
7195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Create new (temporary) file-descriptors before forking, so any errors get
7215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # reported to the main process and set the correct exit-code.
7225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # The mode is provided, since Python otherwise sets a default mode of 0777,
7235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # which would result in the new file having permissions of 0777 & ~umask,
7245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # possibly leaving the executable bits set.
7257d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)  if not os.environ.has_key(LOG_FILE_ENV_VAR):
7267d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    log_file_prefix = "chrome_remote_desktop_%s_" % time.strftime(
7277d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)        '%Y%m%d_%H%M%S', time.localtime(time.time()))
7287d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    log_file = tempfile.NamedTemporaryFile(prefix=log_file_prefix, delete=False)
7297d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    os.environ[LOG_FILE_ENV_VAR] = log_file.name
7307d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    log_fd = log_file.file.fileno()
7317d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)  else:
7327d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    log_fd = os.open(os.environ[LOG_FILE_ENV_VAR],
7337d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)                     os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0600)
7347d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)
7355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  devnull_fd = os.open(os.devnull, os.O_RDONLY)
7367d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)
7377d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)  parent_logger = ParentProcessLogger()
7385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  pid = os.fork()
7405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if pid == 0:
7425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Child process
7435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    os.setsid()
7445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # The second fork ensures that the daemon isn't a session leader, so that
7465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # it doesn't acquire a controlling terminal.
7475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    pid = os.fork()
7485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if pid == 0:
7505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # Grandchild process
7515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      pass
7525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    else:
7535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # Child process
7542a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      os._exit(0)  # pylint: disable=W0212
7555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  else:
7565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Parent process
7577d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    parent_logger.wait_for_logs()
7582a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    os._exit(0)  # pylint: disable=W0212
7595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7607d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)  logging.info("Daemon process started in the background, logging to '%s'" %
7617d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)               os.environ[LOG_FILE_ENV_VAR])
7625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  os.chdir(HOME_DIR)
7645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7657d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)  parent_logger.start_logging()
7667d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)
7675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Copy the file-descriptors to create new stdin, stdout and stderr.  Note
7685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # that dup2(oldfd, newfd) closes newfd first, so this will close the current
7695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # stdin, stdout and stderr, detaching from the terminal.
7705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  os.dup2(devnull_fd, sys.stdin.fileno())
7715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  os.dup2(log_fd, sys.stdout.fileno())
7725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  os.dup2(log_fd, sys.stderr.fileno())
7735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Close the temporary file-descriptors.
7755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  os.close(devnull_fd)
7765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  os.close(log_fd)
7775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def cleanup():
7805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  logging.info("Cleanup.")
7815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  global g_desktops
7835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  for desktop in g_desktops:
7845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if desktop.x_proc:
7855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      logging.info("Terminating Xvfb")
7865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      desktop.x_proc.terminate()
7875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  g_desktops = []
7887d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)  if ParentProcessLogger.instance():
7897d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    ParentProcessLogger.instance().release_parent()
7905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class SignalHandler:
7925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Reload the config file on SIGHUP. Since we pass the configuration to the
7935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  host processes via stdin, they can't reload it, so terminate them. They will
7945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  be relaunched automatically with the new config."""
7955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def __init__(self, host_config):
7975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.host_config = host_config
7985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def __call__(self, signum, _stackframe):
8005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if signum == signal.SIGHUP:
8015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      logging.info("SIGHUP caught, restarting host.")
8022a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      try:
8032a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        self.host_config.load()
8042a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      except (IOError, ValueError) as e:
8052a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        logging.error("Failed to load config: " + str(e))
8065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      for desktop in g_desktops:
8075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if desktop.host_proc:
8085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          desktop.host_proc.send_signal(signal.SIGTERM)
8095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    else:
8105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # Exit cleanly so the atexit handler, cleanup(), gets called.
8115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      raise SystemExit
8125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class RelaunchInhibitor:
8155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Helper class for inhibiting launch of a child process before a timeout has
8165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  elapsed.
8175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  A managed process can be in one of these states:
8195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    running, not inhibited (running == True)
8205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    stopped and inhibited (running == False and is_inhibited() == True)
8215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    stopped but not inhibited (running == False and is_inhibited() == False)
8225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Attributes:
8245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    label: Name of the tracked process. Only used for logging.
8255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    running: Whether the process is currently running.
8265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    earliest_relaunch_time: Time before which the process should not be
8275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      relaunched, or 0 if there is no limit.
8285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    failures: The number of times that the process ran for less than a
8295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      specified timeout, and had to be inhibited.  This count is reset to 0
8305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      whenever the process has run for longer than the timeout.
8315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """
8325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def __init__(self, label):
8345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.label = label
8355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.running = False
8365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.earliest_relaunch_time = 0
8372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    self.earliest_successful_termination = 0
8385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.failures = 0
8395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def is_inhibited(self):
8415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return (not self.running) and (time.time() < self.earliest_relaunch_time)
8425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8432a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  def record_started(self, minimum_lifetime, relaunch_delay):
8445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Record that the process was launched, and set the inhibit time to
8455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    |timeout| seconds in the future."""
8462a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    self.earliest_relaunch_time = time.time() + relaunch_delay
8472a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    self.earliest_successful_termination = time.time() + minimum_lifetime
8485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.running = True
8495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def record_stopped(self):
8515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Record that the process was stopped, and adjust the failure count
8525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    depending on whether the process ran long enough."""
8535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.running = False
8542a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if time.time() < self.earliest_successful_termination:
8555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.failures += 1
8565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    else:
8575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.failures = 0
8585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    logging.info("Failure count for '%s' is now %d", self.label, self.failures)
8595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def relaunch_self():
8625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  cleanup()
8635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  os.execvp(sys.argv[0], sys.argv)
8645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def waitpid_with_timeout(pid, deadline):
8675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Wrapper around os.waitpid() which waits until either a child process dies
8685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  or the deadline elapses.
8695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Args:
8715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    pid: Process ID to wait for, or -1 to wait for any child process.
8725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    deadline: Waiting stops when time.time() exceeds this value.
8735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Returns:
8755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    (pid, status): Same as for os.waitpid(), except that |pid| is 0 if no child
8765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    changed state within the timeout.
8775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Raises:
8795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Same as for os.waitpid().
8805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """
8815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  while time.time() < deadline:
8825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    pid, status = os.waitpid(pid, os.WNOHANG)
8835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if pid != 0:
8845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return (pid, status)
8855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    time.sleep(1)
8865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return (0, 0)
8875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def waitpid_handle_exceptions(pid, deadline):
8905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Wrapper around os.waitpid()/waitpid_with_timeout(), which waits until
8915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  either a child process exits or the deadline elapses, and retries if certain
8925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  exceptions occur.
8935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Args:
8955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    pid: Process ID to wait for, or -1 to wait for any child process.
8965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    deadline: If non-zero, waiting stops when time.time() exceeds this value.
8975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      If zero, waiting stops when a child process exits.
8985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Returns:
9005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    (pid, status): Same as for waitpid_with_timeout(). |pid| is non-zero if and
9015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    only if a child exited during the wait.
9025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
9035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Raises:
9045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Same as for os.waitpid(), except:
9055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      OSError with errno==EINTR causes the wait to be retried (this can happen,
9065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      for example, if this parent process receives SIGHUP).
9075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      OSError with errno==ECHILD means there are no child processes, and so
9085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      this function sleeps until |deadline|. If |deadline| is zero, this is an
9095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      error and the OSError exception is raised in this case.
9105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """
9115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  while True:
9125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    try:
9135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if deadline == 0:
9145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        pid_result, status = os.waitpid(pid, 0)
9155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      else:
9165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        pid_result, status = waitpid_with_timeout(pid, deadline)
9175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return (pid_result, status)
9185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    except OSError, e:
9195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if e.errno == errno.EINTR:
9205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        continue
9215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      elif e.errno == errno.ECHILD:
9225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        now = time.time()
9235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if deadline == 0:
9245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          # No time-limit and no child processes. This is treated as an error
9255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          # (see docstring).
9265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          raise
9275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        elif deadline > now:
9285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          time.sleep(deadline - now)
9295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return (0, 0)
9305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      else:
9315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # Anything else is an unexpected error.
9325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        raise
9335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
9345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
9355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def main():
9365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  EPILOG = """This script is not intended for use by end-users.  To configure
9375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Chrome Remote Desktop, please install the app from the Chrome
9385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Web Store: https://chrome.google.com/remotedesktop"""
9395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  parser = optparse.OptionParser(
9405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      usage="Usage: %prog [options] [ -- [ X server options ] ]",
9415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      epilog=EPILOG)
9425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  parser.add_option("-s", "--size", dest="size", action="append",
9432a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                    help="Dimensions of virtual desktop. This can be specified "
9442a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                    "multiple times to make multiple screen resolutions "
9452a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                    "available (if the Xvfb server supports this).")
9465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  parser.add_option("-f", "--foreground", dest="foreground", default=False,
9475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    action="store_true",
9485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    help="Don't run as a background daemon.")
9495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  parser.add_option("", "--start", dest="start", default=False,
9505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    action="store_true",
9515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    help="Start the host.")
9525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  parser.add_option("-k", "--stop", dest="stop", default=False,
9535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    action="store_true",
9545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    help="Stop the daemon currently running.")
9555d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  parser.add_option("", "--get-status", dest="get_status", default=False,
9565d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                    action="store_true",
9575d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                    help="Prints host status")
9585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  parser.add_option("", "--check-running", dest="check_running", default=False,
9595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    action="store_true",
9605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    help="Return 0 if the daemon is running, or 1 otherwise.")
9612a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  parser.add_option("", "--config", dest="config", action="store",
9622a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                    help="Use the specified configuration file.")
9635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  parser.add_option("", "--reload", dest="reload", default=False,
9645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    action="store_true",
9655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    help="Signal currently running host to reload the config.")
9665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  parser.add_option("", "--add-user", dest="add_user", default=False,
9675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    action="store_true",
9685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    help="Add current user to the chrome-remote-desktop group.")
9695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  parser.add_option("", "--host-version", dest="host_version", default=False,
9705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    action="store_true",
9715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    help="Prints version of the host.")
9725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  (options, args) = parser.parse_args()
9735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
9742a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  # Determine the filename of the host configuration and PID files.
9752a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  if not options.config:
9762a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    options.config = os.path.join(CONFIG_DIR, "host#%s.json" % g_host_hash)
9775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
9785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Check for a modal command-line option (start, stop, etc.)
9795d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
9805d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  if options.get_status:
9816d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)    proc = get_daemon_proc()
9826d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)    if proc is not None:
9835d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      print "STARTED"
9845d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    elif is_supported_platform():
9855d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      print "STOPPED"
9865d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    else:
9875d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      print "NOT_IMPLEMENTED"
9885d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    return 0
9895d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
9905d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  # TODO(sergeyu): Remove --check-running once NPAPI plugin and NM host are
9915d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  # updated to always use get-status flag instead.
9925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if options.check_running:
9936d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)    proc = get_daemon_proc()
9946d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)    return 1 if proc is None else 0
9955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
9965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if options.stop:
9976d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)    proc = get_daemon_proc()
9986d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)    if proc is None:
9995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      print "The daemon is not currently running"
10005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    else:
10016d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)      print "Killing process %s" % proc.pid
10026d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)      proc.terminate()
10036d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)      try:
10046d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)        proc.wait(timeout=30)
10056d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)      except psutil.TimeoutExpired:
10066d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)        print "Timed out trying to kill daemon process"
10076d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)        return 1
10085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return 0
10095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
10105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if options.reload:
10116d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)    proc = get_daemon_proc()
10126d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)    if proc is None:
10135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return 1
10146d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)    proc.send_signal(signal.SIGHUP)
10155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return 0
10165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
10175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if options.add_user:
101858537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)    user = getpass.getuser()
101958537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)    try:
102058537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)      if user in grp.getgrnam(CHROME_REMOTING_GROUP_NAME).gr_mem:
102158537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)        logging.info("User '%s' is already a member of '%s'." %
102258537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)                     (user, CHROME_REMOTING_GROUP_NAME))
102358537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)        return 0
102458537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)    except KeyError:
102558537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)      logging.info("Group '%s' not found." % CHROME_REMOTING_GROUP_NAME)
102658537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)
10272a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if os.getenv("DISPLAY"):
10282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      sudo_command = "gksudo --description \"Chrome Remote Desktop\""
10292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    else:
10302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      sudo_command = "sudo"
10312a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    command = ("sudo -k && exec %(sudo)s -- sh -c "
10325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)               "\"groupadd -f %(group)s && gpasswd --add %(user)s %(group)s\"" %
10335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)               { 'group': CHROME_REMOTING_GROUP_NAME,
103458537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)                 'user': user,
10355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                 'sudo': sudo_command })
10362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    os.execv("/bin/sh", ["/bin/sh", "-c", command])
10372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    return 1
10385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
10395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if options.host_version:
10405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # TODO(sergeyu): Also check RPM package version once we add RPM package.
10415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return os.system(locate_executable(HOST_BINARY_NAME) + " --version") >> 8
10425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
10435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if not options.start:
10445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # If no modal command-line options specified, print an error and exit.
10455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    print >> sys.stderr, EPILOG
10465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return 1
10475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1048cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  # If a RANDR-supporting Xvfb is not available, limit the default size to
1049cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  # something more sensible.
1050cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if get_randr_supporting_x_server():
1051cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    default_sizes = DEFAULT_SIZES
1052cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  else:
1053cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    default_sizes = DEFAULT_SIZE_NO_RANDR
1054cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
10552a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  # Collate the list of sizes that XRANDR should support.
10565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if not options.size:
10572a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if os.environ.has_key(DEFAULT_SIZES_ENV_VAR):
10582a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      default_sizes = os.environ[DEFAULT_SIZES_ENV_VAR]
10592a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    options.size = default_sizes.split(",")
10605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
10615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  sizes = []
10625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  for size in options.size:
10635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    size_components = size.split("x")
10645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if len(size_components) != 2:
10655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      parser.error("Incorrect size format '%s', should be WIDTHxHEIGHT" % size)
10665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
10675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    try:
10685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      width = int(size_components[0])
10695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      height = int(size_components[1])
10705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
10715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # Enforce minimum desktop size, as a sanity-check.  The limit of 100 will
10725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # detect typos of 2 instead of 3 digits.
10735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if width < 100 or height < 100:
10745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        raise ValueError
10755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    except ValueError:
10765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      parser.error("Width and height should be 100 pixels or greater")
10775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
10785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    sizes.append((width, height))
10795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
10802a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  # Register an exit handler to clean up session process and the PID file.
10815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  atexit.register(cleanup)
10825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
10832a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  # Load the initial host configuration.
10842a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  host_config = Config(options.config)
10852a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  try:
10862a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    host_config.load()
10872a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  except (IOError, ValueError) as e:
10882a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    print >> sys.stderr, "Failed to load config: " + str(e)
10892a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    return 1
10905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
10912a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  # Register handler to re-load the configuration in response to signals.
10927d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)  for s in [signal.SIGHUP, signal.SIGINT, signal.SIGTERM]:
10935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    signal.signal(s, SignalHandler(host_config))
10945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
10952a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  # Verify that the initial host configuration has the necessary fields.
10965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  auth = Authentication()
10975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  auth_config_valid = auth.copy_from(host_config)
10985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  host = Host()
10995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  host_config_valid = host.copy_from(host_config)
11005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if not host_config_valid or not auth_config_valid:
11015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    logging.error("Failed to load host configuration.")
11025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return 1
11035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
11042a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  # Determine whether a desktop is already active for the specified host
11052a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  # host configuration.
11066d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)  proc = get_daemon_proc()
11076d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)  if proc is not None:
11085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Debian policy requires that services should "start" cleanly and return 0
11095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # if they are already running.
11105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    print "Service already running."
11115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return 0
11125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
11132a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  # Detach a separate "daemon" process to run the session, unless specifically
11142a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  # requested to run in the foreground.
11155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if not options.foreground:
11167d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    daemonize()
11175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
11185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  logging.info("Using host_id: " + host.host_id)
11195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
11205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  desktop = Desktop(sizes)
11215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
11225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Keep track of the number of consecutive failures of any child process to
11235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # run for longer than a set period of time. The script will exit after a
11245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # threshold is exceeded.
11255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # There is no point in tracking the X session process separately, since it is
11265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # launched at (roughly) the same time as the X server, and the termination of
11275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # one of these triggers the termination of the other.
11285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  x_server_inhibitor = RelaunchInhibitor("X server")
11295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  host_inhibitor = RelaunchInhibitor("host")
11305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  all_inhibitors = [x_server_inhibitor, host_inhibitor]
11315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
11325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Don't allow relaunching the script on the first loop iteration.
11335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  allow_relaunch_self = False
11345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
11355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  while True:
11362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    # Set the backoff interval and exit if a process failed too many times.
11372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    backoff_time = SHORT_BACKOFF_TIME
11385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for inhibitor in all_inhibitors:
11395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if inhibitor.failures >= MAX_LAUNCH_FAILURES:
11405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        logging.error("Too many launch failures of '%s', exiting."
11415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                      % inhibitor.label)
11425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return 1
11432a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      elif inhibitor.failures >= SHORT_BACKOFF_THRESHOLD:
11442a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        backoff_time = LONG_BACKOFF_TIME
11455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
11465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    relaunch_times = []
11475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
11485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # If the session process or X server stops running (e.g. because the user
11495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # logged out), kill the other. This will trigger the next conditional block
11505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # as soon as os.waitpid() reaps its exit-code.
11515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if desktop.session_proc is None and desktop.x_proc is not None:
11525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      logging.info("Terminating X server")
11535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      desktop.x_proc.terminate()
11545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    elif desktop.x_proc is None and desktop.session_proc is not None:
11555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      logging.info("Terminating X session")
11565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      desktop.session_proc.terminate()
11575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    elif desktop.x_proc is None and desktop.session_proc is None:
11585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # Both processes have terminated.
11595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if (allow_relaunch_self and x_server_inhibitor.failures == 0 and
11605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          host_inhibitor.failures == 0):
11615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # Since the user's desktop is already gone at this point, there's no
11625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # state to lose and now is a good time to pick up any updates to this
11635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # script that might have been installed.
11645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        logging.info("Relaunching self")
11655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        relaunch_self()
11665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      else:
11675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # If there is a non-zero |failures| count, restarting the whole script
11685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # would lose this information, so just launch the session as normal.
11695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if x_server_inhibitor.is_inhibited():
11705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          logging.info("Waiting before launching X server")
11715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          relaunch_times.append(x_server_inhibitor.earliest_relaunch_time)
11725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        else:
11735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          logging.info("Launching X server and X session.")
11745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          desktop.launch_session(args)
11752a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          x_server_inhibitor.record_started(MINIMUM_PROCESS_LIFETIME,
11762a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                                            backoff_time)
11775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          allow_relaunch_self = True
11785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
11795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if desktop.host_proc is None:
11805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if host_inhibitor.is_inhibited():
11815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        logging.info("Waiting before launching host process")
11825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        relaunch_times.append(host_inhibitor.earliest_relaunch_time)
11835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      else:
11845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        logging.info("Launching host process")
11855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        desktop.launch_host(host_config)
11862a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        host_inhibitor.record_started(MINIMUM_PROCESS_LIFETIME,
11872a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                                      backoff_time)
11885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
11895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    deadline = min(relaunch_times) if relaunch_times else 0
11905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    pid, status = waitpid_handle_exceptions(-1, deadline)
11915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if pid == 0:
11925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      continue
11935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
11945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    logging.info("wait() returned (%s,%s)" % (pid, status))
11955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
11965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # When a process has terminated, and we've reaped its exit-code, any Popen
11975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # instance for that process is no longer valid. Reset any affected instance
11985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # to None.
11995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if desktop.x_proc is not None and pid == desktop.x_proc.pid:
12005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      logging.info("X server process terminated")
12015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      desktop.x_proc = None
12025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      x_server_inhibitor.record_stopped()
12035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
12045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if desktop.session_proc is not None and pid == desktop.session_proc.pid:
12055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      logging.info("Session process terminated")
12065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      desktop.session_proc = None
12075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
12085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if desktop.host_proc is not None and pid == desktop.host_proc.pid:
12095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      logging.info("Host process terminated")
12105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      desktop.host_proc = None
12117d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)      desktop.host_ready = False
12125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      host_inhibitor.record_stopped()
12135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
12145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # These exit-codes must match the ones used by the host.
12155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # See remoting/host/host_error_codes.h.
12165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # Delete the host or auth configuration depending on the returned error
12175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # code, so the next time this script is run, a new configuration
12185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # will be created and registered.
12197d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)      if os.WIFEXITED(status):
12207d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)        if os.WEXITSTATUS(status) == 100:
12217d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)          logging.info("Host configuration is invalid - exiting.")
12227d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)          return 0
12237d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)        elif os.WEXITSTATUS(status) == 101:
12247d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)          logging.info("Host ID has been deleted - exiting.")
1225a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)          host_config.clear()
12267d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)          host_config.save_and_log_errors()
12277d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)          return 0
12287d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)        elif os.WEXITSTATUS(status) == 102:
12297d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)          logging.info("OAuth credentials are invalid - exiting.")
12307d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)          return 0
12317d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)        elif os.WEXITSTATUS(status) == 103:
12327d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)          logging.info("Host domain is blocked by policy - exiting.")
12337d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)          return 0
12347d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)        # Nothing to do for Mac-only status 104 (login screen unsupported)
12357d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)        elif os.WEXITSTATUS(status) == 105:
12367d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)          logging.info("Username is blocked by policy - exiting.")
12377d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)          return 0
12387d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)        else:
12397d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)          logging.info("Host exited with status %s." % os.WEXITSTATUS(status))
12407d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)      elif os.WIFSIGNALED(status):
12417d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)        logging.info("Host terminated by signal %s." % os.WTERMSIG(status))
12425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
12435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
12445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)if __name__ == "__main__":
12455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  logging.basicConfig(level=logging.DEBUG,
12465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                      format="%(asctime)s:%(levelname)s:%(message)s")
12475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  sys.exit(main())
1248