OLD | NEW |
1 #!/usr/bin/python | 1 #!/usr/bin/python |
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 # Virtual Me2Me implementation. This script runs and manages the processes | 6 # Virtual Me2Me implementation. This script runs and manages the processes |
7 # required for a Virtual Me2Me desktop, which are: X server, X desktop | 7 # required for a Virtual Me2Me desktop, which are: X server, X desktop |
8 # session, and Host process. | 8 # session, and Host process. |
9 # This script is intended to run continuously as a background daemon | 9 # This script is intended to run continuously as a background daemon |
10 # process, running under an ordinary (non-root) user account. | 10 # process, running under an ordinary (non-root) user account. |
(...skipping 113 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
124 | 124 |
125 # Thresholds for switching from fast- to slow-restart and for giving up | 125 # Thresholds for switching from fast- to slow-restart and for giving up |
126 # trying to restart entirely. | 126 # trying to restart entirely. |
127 SHORT_BACKOFF_THRESHOLD = 5 | 127 SHORT_BACKOFF_THRESHOLD = 5 |
128 MAX_LAUNCH_FAILURES = SHORT_BACKOFF_THRESHOLD + 10 | 128 MAX_LAUNCH_FAILURES = SHORT_BACKOFF_THRESHOLD + 10 |
129 | 129 |
130 # Number of seconds to save session output to the log. | 130 # Number of seconds to save session output to the log. |
131 SESSION_OUTPUT_TIME_LIMIT_SECONDS = 30 | 131 SESSION_OUTPUT_TIME_LIMIT_SECONDS = 30 |
132 | 132 |
133 # Globals needed by the atexit cleanup() handler. | 133 # Globals needed by the atexit cleanup() handler. |
134 g_desktops = [] | 134 g_desktop = None |
135 g_host_hash = hashlib.md5(socket.gethostname()).hexdigest() | 135 g_host_hash = hashlib.md5(socket.gethostname()).hexdigest() |
136 | 136 |
137 def gen_xorg_config(sizes): | 137 def gen_xorg_config(sizes): |
138 return ( | 138 return ( |
139 # This causes X to load the default GLX module, even if a proprietary one | 139 # This causes X to load the default GLX module, even if a proprietary one |
140 # is installed in a different directory. | 140 # is installed in a different directory. |
141 'Section "Files"\n' | 141 'Section "Files"\n' |
142 ' ModulePath "/usr/lib/xorg/modules"\n' | 142 ' ModulePath "/usr/lib/xorg/modules"\n' |
143 'EndSection\n' | 143 'EndSection\n' |
144 '\n' | 144 '\n' |
(...skipping 256 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
401 self.host_proc = None | 401 self.host_proc = None |
402 self.child_env = None | 402 self.child_env = None |
403 self.sizes = sizes | 403 self.sizes = sizes |
404 self.xorg_conf = None | 404 self.xorg_conf = None |
405 self.pulseaudio_pipe = None | 405 self.pulseaudio_pipe = None |
406 self.server_supports_exact_resize = False | 406 self.server_supports_exact_resize = False |
407 self.server_supports_randr = False | 407 self.server_supports_randr = False |
408 self.randr_add_sizes = False | 408 self.randr_add_sizes = False |
409 self.host_ready = False | 409 self.host_ready = False |
410 self.ssh_auth_sockname = None | 410 self.ssh_auth_sockname = None |
411 g_desktops.append(self) | 411 global g_desktop |
| 412 assert(g_desktop is None) |
| 413 g_desktop = self |
412 | 414 |
413 @staticmethod | 415 @staticmethod |
414 def get_unused_display_number(): | 416 def get_unused_display_number(): |
415 """Return a candidate display number for which there is currently no | 417 """Return a candidate display number for which there is currently no |
416 X Server lock file""" | 418 X Server lock file""" |
417 display = FIRST_X_DISPLAY_NUMBER | 419 display = FIRST_X_DISPLAY_NUMBER |
418 while os.path.exists(X_LOCK_FILE_TEMPLATE % display): | 420 while os.path.exists(X_LOCK_FILE_TEMPLATE % display): |
419 display += 1 | 421 display += 1 |
420 return display | 422 return display |
421 | 423 |
(...skipping 322 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
744 if self.ssh_auth_sockname: | 746 if self.ssh_auth_sockname: |
745 args.append("--ssh-auth-sockname=%s" % self.ssh_auth_sockname) | 747 args.append("--ssh-auth-sockname=%s" % self.ssh_auth_sockname) |
746 | 748 |
747 args.extend(extra_start_host_args) | 749 args.extend(extra_start_host_args) |
748 | 750 |
749 # Have the host process use SIGUSR1 to signal a successful start. | 751 # Have the host process use SIGUSR1 to signal a successful start. |
750 def sigusr1_handler(signum, frame): | 752 def sigusr1_handler(signum, frame): |
751 _ = signum, frame | 753 _ = signum, frame |
752 logging.info("Host ready to receive connections.") | 754 logging.info("Host ready to receive connections.") |
753 self.host_ready = True | 755 self.host_ready = True |
754 if (ParentProcessLogger.instance() and | 756 if (ParentProcessLogger.instance() and g_desktop is not None and |
755 False not in [desktop.host_ready for desktop in g_desktops]): | 757 g_desktop.host_ready): |
756 ParentProcessLogger.instance().release_parent(True) | 758 ParentProcessLogger.instance().release_parent(True) |
757 | 759 |
758 signal.signal(signal.SIGUSR1, sigusr1_handler) | 760 signal.signal(signal.SIGUSR1, sigusr1_handler) |
759 args.append("--signal-parent") | 761 args.append("--signal-parent") |
760 | 762 |
761 logging.info(args) | 763 logging.info(args) |
762 self.host_proc = subprocess.Popen(args, env=self.child_env, | 764 self.host_proc = subprocess.Popen(args, env=self.child_env, |
763 stdin=subprocess.PIPE) | 765 stdin=subprocess.PIPE) |
764 if not self.host_proc.pid: | 766 if not self.host_proc.pid: |
765 raise Exception("Could not start Chrome Remote Desktop host") | 767 raise Exception("Could not start Chrome Remote Desktop host") |
(...skipping 290 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1056 os.dup2(log_fd, sys.stderr.fileno()) | 1058 os.dup2(log_fd, sys.stderr.fileno()) |
1057 | 1059 |
1058 # Close the temporary file-descriptors. | 1060 # Close the temporary file-descriptors. |
1059 os.close(devnull_fd) | 1061 os.close(devnull_fd) |
1060 os.close(log_fd) | 1062 os.close(log_fd) |
1061 | 1063 |
1062 | 1064 |
1063 def cleanup(): | 1065 def cleanup(): |
1064 logging.info("Cleanup.") | 1066 logging.info("Cleanup.") |
1065 | 1067 |
1066 global g_desktops | 1068 global g_desktop |
1067 for desktop in g_desktops: | 1069 if g_desktop is not None: |
1068 for proc, name in [(desktop.x_proc, "X server"), | 1070 for proc, name in [(g_desktop.x_proc, "X server"), |
1069 (desktop.session_proc, "session"), | 1071 (g_desktop.session_proc, "session"), |
1070 (desktop.host_proc, "host")]: | 1072 (g_desktop.host_proc, "host")]: |
1071 if proc is not None: | 1073 if proc is not None: |
1072 logging.info("Terminating " + name) | 1074 logging.info("Terminating " + name) |
1073 try: | 1075 try: |
1074 psutil_proc = psutil.Process(proc.pid) | 1076 psutil_proc = psutil.Process(proc.pid) |
1075 psutil_proc.terminate() | 1077 psutil_proc.terminate() |
1076 | 1078 |
1077 # Use a short timeout, to avoid delaying service shutdown if the | 1079 # Use a short timeout, to avoid delaying service shutdown if the |
1078 # process refuses to die for some reason. | 1080 # process refuses to die for some reason. |
1079 psutil_proc.wait(timeout=10) | 1081 psutil_proc.wait(timeout=10) |
1080 except psutil.TimeoutExpired: | 1082 except psutil.TimeoutExpired: |
1081 logging.error("Timed out - sending SIGKILL") | 1083 logging.error("Timed out - sending SIGKILL") |
1082 psutil_proc.kill() | 1084 psutil_proc.kill() |
1083 except psutil.Error: | 1085 except psutil.Error: |
1084 logging.error("Error terminating process") | 1086 logging.error("Error terminating process") |
1085 if desktop.xorg_conf is not None: | 1087 if g_desktop.xorg_conf is not None: |
1086 os.remove(desktop.xorg_conf) | 1088 os.remove(g_desktop.xorg_conf) |
1087 | 1089 |
1088 g_desktops = [] | 1090 g_desktop = None |
1089 if ParentProcessLogger.instance(): | 1091 if ParentProcessLogger.instance(): |
1090 ParentProcessLogger.instance().release_parent(False) | 1092 ParentProcessLogger.instance().release_parent(False) |
1091 | 1093 |
1092 class SignalHandler: | 1094 class SignalHandler: |
1093 """Reload the config file on SIGHUP. Since we pass the configuration to the | 1095 """Reload the config file on SIGHUP. Since we pass the configuration to the |
1094 host processes via stdin, they can't reload it, so terminate them. They will | 1096 host processes via stdin, they can't reload it, so terminate them. They will |
1095 be relaunched automatically with the new config.""" | 1097 be relaunched automatically with the new config.""" |
1096 | 1098 |
1097 def __init__(self, host_config): | 1099 def __init__(self, host_config): |
1098 self.host_config = host_config | 1100 self.host_config = host_config |
1099 | 1101 |
1100 def __call__(self, signum, _stackframe): | 1102 def __call__(self, signum, _stackframe): |
1101 if signum == signal.SIGHUP: | 1103 if signum == signal.SIGHUP: |
1102 logging.info("SIGHUP caught, restarting host.") | 1104 logging.info("SIGHUP caught, restarting host.") |
1103 try: | 1105 try: |
1104 self.host_config.load() | 1106 self.host_config.load() |
1105 except (IOError, ValueError) as e: | 1107 except (IOError, ValueError) as e: |
1106 logging.error("Failed to load config: " + str(e)) | 1108 logging.error("Failed to load config: " + str(e)) |
1107 for desktop in g_desktops: | 1109 if g_desktop is not None and g_desktop.host_proc: |
1108 if desktop.host_proc: | 1110 g_desktop.host_proc.send_signal(signal.SIGTERM) |
1109 desktop.host_proc.send_signal(signal.SIGTERM) | |
1110 else: | 1111 else: |
1111 # Exit cleanly so the atexit handler, cleanup(), gets called. | 1112 # Exit cleanly so the atexit handler, cleanup(), gets called. |
1112 raise SystemExit | 1113 raise SystemExit |
1113 | 1114 |
1114 | 1115 |
1115 class RelaunchInhibitor: | 1116 class RelaunchInhibitor: |
1116 """Helper class for inhibiting launch of a child process before a timeout has | 1117 """Helper class for inhibiting launch of a child process before a timeout has |
1117 elapsed. | 1118 elapsed. |
1118 | 1119 |
1119 A managed process can be in one of these states: | 1120 A managed process can be in one of these states: |
(...skipping 501 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1621 else: | 1622 else: |
1622 logging.info("Host exited with status %s." % os.WEXITSTATUS(status)) | 1623 logging.info("Host exited with status %s." % os.WEXITSTATUS(status)) |
1623 elif os.WIFSIGNALED(status): | 1624 elif os.WIFSIGNALED(status): |
1624 logging.info("Host terminated by signal %s." % os.WTERMSIG(status)) | 1625 logging.info("Host terminated by signal %s." % os.WTERMSIG(status)) |
1625 | 1626 |
1626 | 1627 |
1627 if __name__ == "__main__": | 1628 if __name__ == "__main__": |
1628 logging.basicConfig(level=logging.DEBUG, | 1629 logging.basicConfig(level=logging.DEBUG, |
1629 format="%(asctime)s:%(levelname)s:%(message)s") | 1630 format="%(asctime)s:%(levelname)s:%(message)s") |
1630 sys.exit(main()) | 1631 sys.exit(main()) |
OLD | NEW |