1#!/bin/sh
2
3# Copyright (c) 2012 The Chromium Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7NAME=org.chromium.chromoting
8HOST_BUNDLE_NAME=@@HOST_BUNDLE_NAME@@
9PREFPANE_BUNDLE_NAME=@@PREFPANE_BUNDLE_NAME@@
10CONFIG_DIR=/Library/PrivilegedHelperTools
11ENABLED_FILE=$CONFIG_DIR/$NAME.me2me_enabled
12CONFIG_FILE=$CONFIG_DIR/$NAME.json
13HOST_EXE=$CONFIG_DIR/$HOST_BUNDLE_NAME/Contents/MacOS/remoting_me2me_host
14PLIST_FILE=$CONFIG_DIR/$HOST_BUNDLE_NAME/Contents/Info.plist
15PREF_PANE_BUNDLE=/Library/PreferencePanes/$PREFPANE_BUNDLE_NAME
16
17# The exit code returned by 'wait' when a process is terminated by SIGTERM.
18SIGTERM_EXIT_CODE=143
19
20# Range of exit codes returned by the host to indicate that a permanent error
21# has occurred and that the host should not be restarted. Please, keep these
22# constants in sync with remoting/host/host_exit_codes.h.
23MIN_PERMANENT_ERROR_EXIT_CODE=100
24MAX_PERMANENT_ERROR_EXIT_CODE=105
25
26# Constants controlling the host process relaunch throttling.
27MINIMUM_RELAUNCH_INTERVAL=60
28MAXIMUM_HOST_FAILURES=10
29
30# Exit code 126 is defined by Posix to mean "Command found, but not
31# executable", and is returned if the process cannot be launched due to
32# parental control.
33PERMISSION_DENIED_PARENTAL_CONTROL=126
34
35HOST_PID=0
36SIGNAL_WAS_TRAPPED=0
37
38# This script works as a proxy between launchd and the host. Signals of
39# interest to the host must be forwarded.
40SIGNAL_LIST="SIGHUP SIGINT SIGQUIT SIGILL SIGTRAP SIGABRT SIGEMT \
41      SIGFPE SIGKILL SIGBUS SIGSEGV SIGSYS SIGPIPE SIGALRM SIGTERM SIGURG \
42      SIGSTOP SIGTSTP SIGCONT SIGCHLD SIGTTIN SIGTTOU SIGIO SIGXCPU SIGXFSZ \
43      SIGVTALRM SIGPROF SIGWINCH SIGINFO SIGUSR1 SIGUSR2"
44
45handle_signal() {
46  SIGNAL_WAS_TRAPPED=1
47}
48
49run_host() {
50  local host_failure_count=0
51  local host_start_time=0
52
53  while true; do
54    if [[ ! -f "$ENABLED_FILE" ]]; then
55      echo "Daemon is disabled."
56      exit 0
57    fi
58
59    # If this is not the first time the host has run, make sure we don't
60    # relaunch it too soon.
61    if [[ "$host_start_time" -gt 0 ]]; then
62      local host_lifetime=$(($(date +%s) - $host_start_time))
63      echo "Host ran for ${host_lifetime}s"
64      if [[ "$host_lifetime" -lt "$MINIMUM_RELAUNCH_INTERVAL" ]]; then
65        # If the host didn't run for very long, assume it crashed. Relaunch only
66        # after a suitable delay and increase the failure count.
67        host_failure_count=$(($host_failure_count + 1))
68        echo "Host failure count $host_failure_count/$MAXIMUM_HOST_FAILURES"
69        if [[ "$host_failure_count" -ge "$MAXIMUM_HOST_FAILURES" ]]; then
70          echo "Too many host failures. Giving up."
71          exit 1
72        fi
73        local relaunch_in=$(($MINIMUM_RELAUNCH_INTERVAL - $host_lifetime))
74        echo "Relaunching in ${relaunch_in}s"
75        sleep "$relaunch_in"
76      else
77        # If the host ran for long enough, reset the crash counter.
78        host_failure_count=0
79      fi
80    fi
81
82    # Execute the host asynchronously and forward signals to it.
83    trap "handle_signal" $SIGNAL_LIST
84    host_start_time=$(date +%s)
85    "$HOST_EXE" --host-config="$CONFIG_FILE" &
86    HOST_PID="$!"
87
88    # Wait for the host to return and process its exit code.
89    while true; do
90      wait "$HOST_PID"
91      EXIT_CODE="$?"
92      if [[ $SIGNAL_WAS_TRAPPED -eq 1 ]]; then
93        # 'wait' returned as the result of a trapped signal and the exit code is
94        # the signal that was trapped + 128. Forward the signal to the host.
95        SIGNAL_WAS_TRAPPED=0
96        local SIGNAL=$(($EXIT_CODE - 128))
97        echo "Forwarding signal $SIGNAL to host"
98        kill -$SIGNAL "$HOST_PID"
99      elif [[ "$EXIT_CODE" -eq "0" ||
100              "$EXIT_CODE" -eq "$SIGTERM_EXIT_CODE" ||
101              "$EXIT_CODE" -eq "$PERMISSION_DENIED_PARENTAL_CONTROL" ||
102              ("$EXIT_CODE" -ge "$MIN_PERMANENT_ERROR_EXIT_CODE" && \
103              "$EXIT_CODE" -le "$MAX_PERMANENT_ERROR_EXIT_CODE") ]]; then
104        echo "Host returned permanent exit code $EXIT_CODE at ""$(date)"""
105        if [[ "$EXIT_CODE" -eq 101 ]]; then
106          # Exit code 101 is "hostID deleted", which indicates that the host
107          # was taken off-line remotely. To prevent the host being restarted
108          # when the login context changes, try to delete the "enabled" file.
109          # Since this requires root privileges, this is only possible when
110          # this script is launched in the "login" context. In the "aqua"
111          # context, just exit and try again next time.
112          echo "Host id deleted - disabling"
113          rm -f "$ENABLED_FILE" 2>/dev/null
114        fi
115        exit "$EXIT_CODE"
116      else
117        # Ignore non-permanent error-code and launch host again. Stop handling
118        # signals temporarily in case the script has to sleep to throttle host
119        # relaunches. While throttling, there is no host process to which to
120        # forward the signal, so the default behaviour should be restored.
121        echo "Host returned non-permanent exit code $EXIT_CODE at ""$(date)"""
122        trap - $SIGNAL_LIST
123        HOST_PID=0
124        break
125      fi
126    done
127  done
128}
129
130if [[ "$1" = "--disable" ]]; then
131  # This script is executed from base::mac::ExecuteWithPrivilegesAndWait(),
132  # which requires the child process to write its PID to stdout before
133  # anythine else. See base/mac/authorization_util.h for details.
134  echo $$
135  rm -f "$ENABLED_FILE"
136elif [[ "$1" = "--enable" ]]; then
137  echo $$
138  # Ensure the config file is private whilst being written.
139  rm -f "$CONFIG_FILE"
140  umask 0077
141  cat > "$CONFIG_FILE"
142  # Ensure the config is readable by the user registering the host.
143  chmod +a "$USER:allow:read" "$CONFIG_FILE"
144  touch "$ENABLED_FILE"
145elif [[ "$1" = "--save-config" ]]; then
146  echo $$
147  cat > "$CONFIG_FILE"
148elif [[ "$1" = "--host-version" ]]; then
149  /usr/libexec/PlistBuddy -c "Print CFBundleVersion" "$PLIST_FILE"
150elif [[ "$1" = "--relaunch-prefpane" ]]; then
151  # Wait for the parent (System Preferences applet) to die, by reading from
152  # stdin until the pipe is broken.
153  cat 2>/dev/null || true
154  open "$PREF_PANE_BUNDLE"
155elif [[ "$1" = "--run-from-launchd" ]]; then
156  echo Host started for user $USER at $"$(date)"
157  run_host
158else
159  echo $$
160  exit 1
161fi
162